mirror of
https://github.com/lightninglabs/aperture.git
synced 2026-01-31 15:14:26 +01:00
Merge pull request #12 from wpaulino/lsat-mint
multi: introduce proper LSAT creation and verification
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/lightninglabs/kirin/macaroons"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
@@ -36,8 +38,7 @@ var (
|
||||
// LsatAuthenticator is an authenticator that uses the LSAT protocol to
|
||||
// authenticate requests.
|
||||
type LsatAuthenticator struct {
|
||||
challenger Challenger
|
||||
macService *macaroons.Service
|
||||
minter Minter
|
||||
}
|
||||
|
||||
// A compile time flag to ensure the LsatAuthenticator satisfies the
|
||||
@@ -46,44 +47,35 @@ var _ Authenticator = (*LsatAuthenticator)(nil)
|
||||
|
||||
// NewLsatAuthenticator creates a new authenticator that authenticates requests
|
||||
// based on LSAT tokens.
|
||||
func NewLsatAuthenticator(challenger Challenger) (*LsatAuthenticator, error) {
|
||||
macService, err := macaroons.NewService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LsatAuthenticator{
|
||||
challenger: challenger,
|
||||
macService: macService,
|
||||
}, nil
|
||||
func NewLsatAuthenticator(minter Minter) *LsatAuthenticator {
|
||||
return &LsatAuthenticator{minter: minter}
|
||||
}
|
||||
|
||||
// Accept returns whether or not the header successfully authenticates the user
|
||||
// to a given backend service.
|
||||
//
|
||||
// NOTE: This is part of the Authenticator interface.
|
||||
func (l *LsatAuthenticator) Accept(header *http.Header) bool {
|
||||
func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool {
|
||||
// Try reading the macaroon and preimage from the HTTP header. This can
|
||||
// be in different header fields depending on the implementation and/or
|
||||
// protocol.
|
||||
mac, preimageBytes, err := FromHeader(header)
|
||||
mac, preimage, err := FromHeader(header)
|
||||
if err != nil {
|
||||
log.Debugf("Deny: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(guggero): check preimage against payment hash caveat in the
|
||||
// macaroon.
|
||||
if len(preimageBytes) != 32 {
|
||||
log.Debugf("Deny: Decoded preimage has invalid length.")
|
||||
verificationParams := &mint.VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: preimage,
|
||||
TargetService: serviceName,
|
||||
}
|
||||
err = l.minter.VerifyLSAT(context.Background(), verificationParams)
|
||||
if err != nil {
|
||||
log.Debugf("Deny: LSAT validation failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = l.macService.ValidateMacaroon(mac, []bakery.Op{})
|
||||
if err != nil {
|
||||
log.Debugf("Deny: Macaroon validation failed: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -91,34 +83,24 @@ func (l *LsatAuthenticator) Accept(header *http.Header) bool {
|
||||
// complete.
|
||||
//
|
||||
// NOTE: This is part of the Authenticator interface.
|
||||
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request) (
|
||||
http.Header, error) {
|
||||
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request,
|
||||
serviceName string) (http.Header, error) {
|
||||
|
||||
paymentRequest, paymentHash, err := l.challenger.NewChallenge()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating new challenge: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new macaroon and add the payment hash as a caveat.
|
||||
// The bakery requires at least one operation so we add an "allow all"
|
||||
// permission set for now.
|
||||
mac, err := l.macService.NewMacaroon(
|
||||
[]bakery.Op{{Entity: opWildcard, Action: opWildcard}}, []string{
|
||||
checkers.Condition(
|
||||
macaroons.CondRHash, paymentHash.String(),
|
||||
),
|
||||
},
|
||||
service := lsat.Service{Name: serviceName, Tier: lsat.BaseTier}
|
||||
mac, paymentRequest, err := l.minter.MintLSAT(
|
||||
context.Background(), service,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating macaroon: %v", err)
|
||||
log.Errorf("Error minting LSAT: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
macBytes, err := mac.MarshalBinary()
|
||||
if err != nil {
|
||||
log.Errorf("Error serializing LSAT: %v", err)
|
||||
}
|
||||
|
||||
str := "LSAT macaroon='%s' invoice='%s'"
|
||||
str = fmt.Sprintf(
|
||||
str, base64.StdEncoding.EncodeToString(mac), paymentRequest,
|
||||
)
|
||||
str := fmt.Sprintf("LSAT macaroon='%s' invoice='%s'",
|
||||
base64.StdEncoding.EncodeToString(macBytes), paymentRequest)
|
||||
header := r.Header
|
||||
header.Set("WWW-Authenticate", str)
|
||||
|
||||
@@ -134,7 +116,7 @@ func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request) (
|
||||
// 3. Macaroon: <macHex>
|
||||
// If only the macaroon is sent in header 2 or three then it is expected to have
|
||||
// a caveat with the preimage attached to it.
|
||||
func FromHeader(header *http.Header) (*macaroon.Macaroon, []byte, error) {
|
||||
func FromHeader(header *http.Header) (*macaroon.Macaroon, lntypes.Preimage, error) {
|
||||
var authHeader string
|
||||
|
||||
switch {
|
||||
@@ -147,37 +129,37 @@ func FromHeader(header *http.Header) (*macaroon.Macaroon, []byte, error) {
|
||||
log.Debugf("Trying to authorize with header value [%s].",
|
||||
authHeader)
|
||||
if !authRegex.MatchString(authHeader) {
|
||||
return nil, nil, fmt.Errorf("invalid auth header "+
|
||||
"format: %s", authHeader)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("invalid "+
|
||||
"auth header format: %s", authHeader)
|
||||
}
|
||||
matches := authRegex.FindStringSubmatch(authHeader)
|
||||
if len(matches) != 3 {
|
||||
return nil, nil, fmt.Errorf("invalid auth header "+
|
||||
"format: %s", authHeader)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("invalid "+
|
||||
"auth header format: %s", authHeader)
|
||||
}
|
||||
|
||||
// Decode the content of the two parts of the header value.
|
||||
macBase64, preimageHex := matches[1], matches[2]
|
||||
macBytes, err := base64.StdEncoding.DecodeString(macBase64)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("base64 decode of "+
|
||||
"macaroon failed: %v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("base64 "+
|
||||
"decode of macaroon failed: %v", err)
|
||||
}
|
||||
mac := &macaroon.Macaroon{}
|
||||
err = mac.UnmarshalBinary(macBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to unmarshal "+
|
||||
"macaroon: %v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("unable to "+
|
||||
"unmarshal macaroon: %v", err)
|
||||
}
|
||||
preimageBytes, err := hex.DecodeString(preimageHex)
|
||||
preimage, err := lntypes.MakePreimageFromStr(preimageHex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("hex decode of preimage "+
|
||||
"failed: %v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("hex "+
|
||||
"decode of preimage failed: %v", err)
|
||||
}
|
||||
|
||||
// All done, we don't need to extract anything from the
|
||||
// macaroon since the preimage was presented separately.
|
||||
return mac, preimageBytes, nil
|
||||
return mac, preimage, nil
|
||||
|
||||
// Header field 2: Contains only the macaroon.
|
||||
case header.Get(HeaderMacaroonMD) != "":
|
||||
@@ -188,40 +170,41 @@ func FromHeader(header *http.Header) (*macaroon.Macaroon, []byte, error) {
|
||||
authHeader = header.Get(HeaderMacaroon)
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("no auth header provided")
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("no auth header " +
|
||||
"provided")
|
||||
}
|
||||
|
||||
// For case 2 and 3, we need to actually unmarshal the macaroon to
|
||||
// extract the preimage.
|
||||
macBytes, err := hex.DecodeString(authHeader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("hex decode of macaroon "+
|
||||
"failed: %v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("hex decode of "+
|
||||
"macaroon failed: %v", err)
|
||||
}
|
||||
mac := &macaroon.Macaroon{}
|
||||
err = mac.UnmarshalBinary(macBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to unmarshal macaroon: "+
|
||||
"%v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("unable to "+
|
||||
"unmarshal macaroon: %v", err)
|
||||
}
|
||||
preimageHex, err := macaroons.ExtractCaveat(mac, macaroons.CondPreimage)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to extract preimage from "+
|
||||
"macaroon: %v", err)
|
||||
preimageHex, ok := lsat.HasCaveat(mac, lsat.PreimageKey)
|
||||
if !ok {
|
||||
return nil, lntypes.Preimage{}, errors.New("preimage caveat " +
|
||||
"not found")
|
||||
}
|
||||
preimageBytes, err := hex.DecodeString(preimageHex)
|
||||
preimage, err := lntypes.MakePreimageFromStr(preimageHex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("hex decode of preimage "+
|
||||
"failed: %v", err)
|
||||
return nil, lntypes.Preimage{}, fmt.Errorf("hex decode of "+
|
||||
"preimage failed: %v", err)
|
||||
}
|
||||
|
||||
return mac, preimageBytes, nil
|
||||
return mac, preimage, nil
|
||||
}
|
||||
|
||||
// SetHeader sets the provided authentication elements as the default/standard
|
||||
// HTTP header for the LSAT protocol.
|
||||
func SetHeader(header *http.Header, mac *macaroon.Macaroon,
|
||||
preimage []byte) error {
|
||||
preimage lntypes.Preimage) error {
|
||||
|
||||
macBytes, err := mac.MarshalBinary()
|
||||
if err != nil {
|
||||
@@ -229,7 +212,7 @@ func SetHeader(header *http.Header, mac *macaroon.Macaroon,
|
||||
}
|
||||
value := fmt.Sprintf(
|
||||
authFormat, base64.StdEncoding.EncodeToString(macBytes),
|
||||
hex.EncodeToString(preimage),
|
||||
preimage.String(),
|
||||
)
|
||||
header.Set(HeaderAuthorization, value)
|
||||
return nil
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightninglabs/kirin/auth"
|
||||
"github.com/lightninglabs/kirin/macaroons"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
@@ -27,9 +27,8 @@ func createDummyMacHex(preimage string) string {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dummyMac.AddFirstPartyCaveat(
|
||||
[]byte(macaroons.CondPreimage + " " + preimage),
|
||||
)
|
||||
preimageCaveat := lsat.Caveat{Condition: lsat.PreimageKey, Value: preimage}
|
||||
err = lsat.AddFirstPartyCaveats(dummyMac, preimageCaveat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -135,13 +134,9 @@ func TestLsatAuthenticator(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
a, err := auth.NewLsatAuthenticator(&mockChallenger{})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create authenticator: %v", err)
|
||||
}
|
||||
|
||||
a := auth.NewLsatAuthenticator(&mockMint{})
|
||||
for _, testCase := range headerTests {
|
||||
result := a.Accept(testCase.header)
|
||||
result := a.Accept(testCase.header, "test")
|
||||
if result != testCase.result {
|
||||
t.Fatalf("test case %s failed. got %v expected %v",
|
||||
testCase.id, result, testCase.result)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
// Authenticator is the generic interface for validating client headers and
|
||||
@@ -11,16 +14,19 @@ import (
|
||||
type Authenticator interface {
|
||||
// Accept returns whether or not the header successfully authenticates
|
||||
// the user to a given backend service.
|
||||
Accept(*http.Header) bool
|
||||
Accept(*http.Header, string) bool
|
||||
|
||||
// FreshChallengeHeader returns a header containing a challenge for the
|
||||
// user to complete.
|
||||
FreshChallengeHeader(r *http.Request) (http.Header, error)
|
||||
FreshChallengeHeader(*http.Request, string) (http.Header, error)
|
||||
}
|
||||
|
||||
// Challenger is an interface for generating new payment challenges.
|
||||
type Challenger interface {
|
||||
// NewChallenge creates a new LSAT payment challenge, returning a
|
||||
// payment request (invoice) and the corresponding payment hash.
|
||||
NewChallenge() (string, lntypes.Hash, error)
|
||||
// Minter is an entity that is able to mint and verify LSATs for a set of
|
||||
// services.
|
||||
type Minter interface {
|
||||
// MintLSAT mints a new LSAT for the target services.
|
||||
MintLSAT(context.Context, ...lsat.Service) (*macaroon.Macaroon, string, error)
|
||||
|
||||
// VerifyLSAT attempts to verify an LSAT with the given parameters.
|
||||
VerifyLSAT(context.Context, *mint.VerificationParams) error
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import "net/http"
|
||||
// MockAuthenticator is a mock implementation of the authenticator.
|
||||
type MockAuthenticator struct{}
|
||||
|
||||
// A compile-time constraint to ensure MockAuthenticator implements
|
||||
// Authenticator.
|
||||
var _ Authenticator = (*MockAuthenticator)(nil)
|
||||
|
||||
// NewMockAuthenticator returns a new MockAuthenticator instance.
|
||||
func NewMockAuthenticator() *MockAuthenticator {
|
||||
return &MockAuthenticator{}
|
||||
@@ -12,7 +16,7 @@ func NewMockAuthenticator() *MockAuthenticator {
|
||||
|
||||
// Accept returns whether or not the header successfully authenticates the user
|
||||
// to a given backend service.
|
||||
func (a MockAuthenticator) Accept(header *http.Header) bool {
|
||||
func (a MockAuthenticator) Accept(header *http.Header, _ string) bool {
|
||||
if header.Get("Authorization") != "" {
|
||||
return true
|
||||
}
|
||||
@@ -27,7 +31,9 @@ func (a MockAuthenticator) Accept(header *http.Header) bool {
|
||||
|
||||
// FreshChallengeHeader returns a header containing a challenge for the user to
|
||||
// complete.
|
||||
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request) (http.Header, error) {
|
||||
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request,
|
||||
_ string) (http.Header, error) {
|
||||
|
||||
header := r.Header
|
||||
header.Set("WWW-Authenticate", "LSAT macaroon='AGIAJEemVQUTEyNCR0exk7ek90Cg==' invoice='lnbc1500n1pw5kjhmpp5fu6xhthlt2vucmzkx6c7wtlh2r625r30cyjsfqhu8rsx4xpz5lwqdpa2fjkzep6yptksct5yp5hxgrrv96hx6twvusycn3qv9jx7ur5d9hkugr5dusx6cqzpgxqr23s79ruapxc4j5uskt4htly2salw4drq979d7rcela9wz02elhypmdzmzlnxuknpgfyfm86pntt8vvkvffma5qc9n50h4mvqhngadqy3ngqjcym5a'")
|
||||
return header, nil
|
||||
|
||||
25
auth/mock_test.go
Normal file
25
auth/mock_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/lightninglabs/kirin/auth"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
type mockMint struct {
|
||||
}
|
||||
|
||||
var _ auth.Minter = (*mockMint)(nil)
|
||||
|
||||
func (m *mockMint) MintLSAT(_ context.Context,
|
||||
services ...lsat.Service) (*macaroon.Macaroon, string, error) {
|
||||
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
func (m *mockMint) VerifyLSAT(_ context.Context, p *mint.VerificationParams) error {
|
||||
return nil
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightninglabs/kirin/auth"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lndclient"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
@@ -22,13 +22,13 @@ type LndChallenger struct {
|
||||
}
|
||||
|
||||
// A compile time flag to ensure the LndChallenger satisfies the
|
||||
// Challenger interface.
|
||||
var _ auth.Challenger = (*LndChallenger)(nil)
|
||||
// mint.Challenger interface.
|
||||
var _ mint.Challenger = (*LndChallenger)(nil)
|
||||
|
||||
// NewLndChallenger creates a new challenger that uses the given connection
|
||||
// details to connect to an lnd backend to create payment challenges.
|
||||
func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
|
||||
auth.Challenger, error) {
|
||||
*LndChallenger, error) {
|
||||
|
||||
if genInvoiceReq == nil {
|
||||
return nil, fmt.Errorf("genInvoiceReq cannot be nil")
|
||||
|
||||
@@ -16,6 +16,12 @@ var (
|
||||
defaultMaxLogFileSize = 10
|
||||
)
|
||||
|
||||
type etcdConfig struct {
|
||||
Host string `long:"host" description:"host:port of an active etcd instance"`
|
||||
User string `long:"user" description:"user authorized to access the etcd host"`
|
||||
Password string `long:"password" description:"password of the etcd user"`
|
||||
}
|
||||
|
||||
type authConfig struct {
|
||||
// LndHost is the hostname of the LND instance to connect to.
|
||||
LndHost string `long:"lndhost" description:"Hostname of the LND instance to connect to"`
|
||||
@@ -36,6 +42,8 @@ type config struct {
|
||||
// is located.
|
||||
StaticRoot string `long:"staticroot" description:"The folder where the static content is located."`
|
||||
|
||||
Etcd *etcdConfig `long:"etcd" description:"Configuration for the etcd instance backing the proxy."`
|
||||
|
||||
Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."`
|
||||
|
||||
// Services is a list of JSON objects in string format, which specify
|
||||
|
||||
23
go.mod
23
go.mod
@@ -5,15 +5,32 @@ go 1.13
|
||||
require (
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
||||
github.com/coreos/etcd v3.3.17+incompatible
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/lightninglabs/loop v0.2.4-alpha.0.20191116024025-539d6ed9e3e8
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191119135609-79051ac63f1a
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/gorilla/websocket v1.4.1 // indirect
|
||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.8 // indirect
|
||||
github.com/lightninglabs/loop v0.3.0-alpha.0.20191126005026-8b8b87844035
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191115230031-4d7a151b4763
|
||||
github.com/lightningnetwork/lnd/cert v1.0.0
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
go.uber.org/zap v1.13.0 // indirect
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 // indirect
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
|
||||
google.golang.org/grpc v1.25.1
|
||||
gopkg.in/macaroon-bakery.v2 v2.1.0
|
||||
gopkg.in/macaroon-bakery.v2 v2.1.0 // indirect
|
||||
gopkg.in/macaroon.v2 v2.1.0
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||
)
|
||||
|
||||
78
go.sum
78
go.sum
@@ -1,6 +1,7 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||
@@ -61,9 +62,20 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo=
|
||||
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 h1:FE783w8WFh+Rvg+7bZ5g8p7gP4SeVS4AoNwkvazlsBg=
|
||||
github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
@@ -84,16 +96,26 @@ github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
@@ -110,8 +132,12 @@ github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH04
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
|
||||
@@ -131,6 +157,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -140,15 +167,14 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||
github.com/lightninglabs/loop v0.2.4-alpha.0.20191116024025-539d6ed9e3e8 h1:ZH3Qd9f5jBoNdWDP9jkzBdsuKXwFB0fkEEnax3hPt3Q=
|
||||
github.com/lightninglabs/loop v0.2.4-alpha.0.20191116024025-539d6ed9e3e8/go.mod h1:9tvOOyUhd3AcfrxLz/dJSTHJ0ouqA+u6utJ+fBYrk9M=
|
||||
github.com/lightninglabs/loop v0.3.0-alpha.0.20191126005026-8b8b87844035 h1:Sw8Ibk3xIZgneHvmAcob1uVz55DYn8mvvjbcpH9CH0w=
|
||||
github.com/lightninglabs/loop v0.3.0-alpha.0.20191126005026-8b8b87844035/go.mod h1:9tvOOyUhd3AcfrxLz/dJSTHJ0ouqA+u6utJ+fBYrk9M=
|
||||
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
|
||||
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a h1:GoWPN4i4jTKRxhVNh9a2vvBBO1Y2seiJB+SopUYoKyo=
|
||||
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191115230031-4d7a151b4763 h1:Eit9hH737th2WwnnVhkuxyMqgXce6keztGTtbDMLJ80=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191115230031-4d7a151b4763/go.mod h1:Z7DDVIgvMgyb/4+btLeiU++xt49T35PNunXGCvAaxiE=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191119135609-79051ac63f1a h1:0N5HflbWT8QjrFh9UZ8qJj6VU86YDeCJ4LtHCetMFUM=
|
||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191119135609-79051ac63f1a/go.mod h1:51tWqgjX5ZfYOLTlgkn7fQXCODJPUD3d1t1CsV1DKS4=
|
||||
github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw=
|
||||
github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
|
||||
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
|
||||
@@ -162,6 +188,11 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -171,6 +202,9 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
@@ -178,6 +212,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
@@ -189,21 +224,42 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -211,6 +267,9 @@ golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTk
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -222,6 +281,7 @@ golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
||||
@@ -255,6 +315,11 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
@@ -276,6 +341,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
||||
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
|
||||
@@ -296,3 +362,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
54
kirin.go
54
kirin.go
@@ -6,8 +6,11 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/lightninglabs/kirin/auth"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/kirin/proxy"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/cert"
|
||||
@@ -15,6 +18,16 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// topLevelKey is the top level key for an etcd cluster where we'll
|
||||
// store all LSAT proxy related data.
|
||||
topLevelKey = "lsat/proxy"
|
||||
|
||||
// etcdKeyDelimeter is the delimeter we'll use for all etcd keys to
|
||||
// represent a path-like structure.
|
||||
etcdKeyDelimeter = "/"
|
||||
)
|
||||
|
||||
// Main is the true entrypoint of Kirin.
|
||||
func Main() {
|
||||
// TODO: Prevent from running twice.
|
||||
@@ -39,6 +52,17 @@ func start() error {
|
||||
return fmt.Errorf("unable to set up logging: %v", err)
|
||||
}
|
||||
|
||||
// Initialize our etcd client.
|
||||
etcdClient, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: []string{cfg.Etcd.Host},
|
||||
DialTimeout: 5 * time.Second,
|
||||
Username: cfg.Etcd.User,
|
||||
Password: cfg.Etcd.Password,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to etcd: %v", err)
|
||||
}
|
||||
|
||||
// Create the proxy and connect it to lnd.
|
||||
genInvoiceReq := func() (*lnrpc.Invoice, error) {
|
||||
return &lnrpc.Invoice{
|
||||
@@ -46,7 +70,10 @@ func start() error {
|
||||
Value: 1,
|
||||
}, nil
|
||||
}
|
||||
servicesProxy, err := createProxy(cfg, genInvoiceReq)
|
||||
servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: http.HandlerFunc(servicesProxy.ServeHTTP),
|
||||
@@ -70,7 +97,7 @@ func start() error {
|
||||
// The ListenAndServeTLS below will block until shut down or an error
|
||||
// occurs. So we can just defer a cleanup function here that will close
|
||||
// everything on shutdown.
|
||||
defer cleanup(server)
|
||||
defer cleanup(etcdClient, server)
|
||||
|
||||
// Finally start the server.
|
||||
log.Infof("Starting the server, listening on %s.", cfg.ListenAddr)
|
||||
@@ -127,24 +154,27 @@ func setupLogging(cfg *config) error {
|
||||
}
|
||||
|
||||
// createProxy creates the proxy with all the services it needs.
|
||||
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator) (
|
||||
*proxy.Proxy, error) {
|
||||
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator,
|
||||
etcdClient *clientv3.Client) (*proxy.Proxy, error) {
|
||||
|
||||
challenger, err := NewLndChallenger(
|
||||
cfg.Authenticator, genInvoiceReq,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authenticator, err := auth.NewLsatAuthenticator(challenger)
|
||||
challenger, err := NewLndChallenger(cfg.Authenticator, genInvoiceReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minter := mint.New(&mint.Config{
|
||||
Challenger: challenger,
|
||||
Secrets: newSecretStore(etcdClient),
|
||||
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
|
||||
})
|
||||
authenticator := auth.NewLsatAuthenticator(minter)
|
||||
return proxy.New(authenticator, cfg.Services, cfg.StaticRoot)
|
||||
}
|
||||
|
||||
// cleanup closes the given server and shuts down the log rotator.
|
||||
func cleanup(server *http.Server) {
|
||||
func cleanup(etcdClient *clientv3.Client, server *http.Server) {
|
||||
if err := etcdClient.Close(); err != nil {
|
||||
log.Errorf("Error terminating etcd client: %v", err)
|
||||
}
|
||||
err := server.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Error closing server: %v", err)
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package macaroons
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// CondRHash is the macaroon caveat condition for a payment hash.
|
||||
CondRHash = "r-hash"
|
||||
|
||||
// CondPreimage is the macaroon caveat condition for a payment preimage.
|
||||
CondPreimage = "preimage"
|
||||
)
|
||||
|
||||
var (
|
||||
rootKey = "aabbccddeeff00112233445566778899"
|
||||
rootKeyId = []byte("0")
|
||||
)
|
||||
|
||||
type rootKeyStore struct{}
|
||||
|
||||
// A compile time flag to ensure the rootKeyStore satisfies the
|
||||
// bakery.RootKeyStore interface.
|
||||
var _ bakery.RootKeyStore = (*rootKeyStore)(nil)
|
||||
|
||||
func (r *rootKeyStore) Get(_ context.Context, id []byte) ([]byte, error) {
|
||||
return hex.DecodeString(rootKey)
|
||||
}
|
||||
|
||||
func (r *rootKeyStore) RootKey(_ context.Context) (rootKey, id []byte,
|
||||
err error) {
|
||||
|
||||
key, err := r.Get(nil, rootKeyId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, rootKeyId, nil
|
||||
}
|
||||
|
||||
// Service can bake and validate macaroons.
|
||||
type Service struct {
|
||||
bakery.Bakery
|
||||
}
|
||||
|
||||
// NewService creates a new macaroon service with the given checker functions
|
||||
// that should be supported when validating a macaroon.
|
||||
func NewService(checks ...macaroons.Checker) (*Service, error) {
|
||||
macaroonParams := bakery.BakeryParams{
|
||||
Location: "kirin",
|
||||
RootKeyStore: &rootKeyStore{},
|
||||
Locator: nil,
|
||||
Key: nil,
|
||||
}
|
||||
|
||||
svc := bakery.New(macaroonParams)
|
||||
|
||||
// Register all custom caveat checkers with the bakery's checker.
|
||||
checker := svc.Checker.FirstPartyCaveatChecker.(*checkers.Checker)
|
||||
for _, check := range checks {
|
||||
cond, fun := check()
|
||||
if !isRegistered(checker, cond) {
|
||||
checker.Register(cond, "std", fun)
|
||||
}
|
||||
}
|
||||
|
||||
return &Service{*svc}, nil
|
||||
}
|
||||
|
||||
// NewMacaroon bakes a new macaroon with the given allowed operations and
|
||||
// optional first-party caveats.
|
||||
func (s *Service) NewMacaroon(operations []bakery.Op, caveats []string) (
|
||||
[]byte, error) {
|
||||
|
||||
ctx := context.Background()
|
||||
mac, err := s.Oven.NewMacaroon(
|
||||
ctx, bakery.LatestVersion, nil, operations...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add all first party caveats before serializing the macaroon.
|
||||
for _, caveat := range caveats {
|
||||
err := mac.M().AddFirstPartyCaveat([]byte(caveat))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
macBytes, err := mac.M().MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return macBytes, nil
|
||||
}
|
||||
|
||||
// ValidateMacaroon verifies the signature chain of a macaroon and then
|
||||
// checks that none of the applied restrictions are violated.
|
||||
func (s *Service) ValidateMacaroon(mac *macaroon.Macaroon,
|
||||
perms []bakery.Op) error {
|
||||
|
||||
// Check the method being called against the permitted operation and
|
||||
// the expiration time and IP address and return the result.
|
||||
authChecker := s.Checker.Auth(macaroon.Slice{mac})
|
||||
_, err := authChecker.Allow(context.Background(), perms...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractCaveat extracts the value of a given caveat condition or returns an
|
||||
// empty string if that caveat does not exist.
|
||||
func ExtractCaveat(mac *macaroon.Macaroon, cond string) (string, error) {
|
||||
if mac == nil {
|
||||
return "", fmt.Errorf("macaroon cannot be nil")
|
||||
}
|
||||
for _, caveat := range mac.Caveats() {
|
||||
cavStr := string(caveat.Id)
|
||||
cavCond, cavArg, err := checkers.ParseCaveat(cavStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if cavCond == cond {
|
||||
return cavArg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// isRegistered checks to see if the required checker has already been
|
||||
// registered in order to avoid a panic caused by double registration.
|
||||
func isRegistered(c *checkers.Checker, name string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, info := range c.Info() {
|
||||
if info.Name == name &&
|
||||
info.Prefix == "" &&
|
||||
info.Namespace == "std" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
256
mint/mint.go
Normal file
256
mint/mint.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSecretNotFound is an error returned when we attempt to retrieve a
|
||||
// secret by its key but it is not found.
|
||||
ErrSecretNotFound = errors.New("secret not found")
|
||||
)
|
||||
|
||||
// Challenger is an interface used to present requesters of LSATs with a
|
||||
// challenge that must be satisfied before an LSAT can be validated. This
|
||||
// challenge takes the form of a Lightning payment request.
|
||||
type Challenger interface {
|
||||
// NewChallenge returns a new challenge in the form of a Lightning
|
||||
// payment request. The payment hash is also returned as a convenience
|
||||
// to avoid having to decode the payment request in order to retrieve
|
||||
// its payment hash.
|
||||
NewChallenge() (string, lntypes.Hash, error)
|
||||
}
|
||||
|
||||
// SecretStore is the store responsible for storing LSAT secrets. These secrets
|
||||
// are required for proper verification of each minted LSAT.
|
||||
type SecretStore interface {
|
||||
// NewSecret creates a new cryptographically random secret which is
|
||||
// keyed by the given hash.
|
||||
NewSecret(context.Context, [sha256.Size]byte) ([lsat.SecretSize]byte, error)
|
||||
|
||||
// GetSecret returns the cryptographically random secret that
|
||||
// corresponds to the given hash. If there is no secret, then
|
||||
// ErrSecretNotFound is returned.
|
||||
GetSecret(context.Context, [sha256.Size]byte) ([lsat.SecretSize]byte, error)
|
||||
|
||||
// RevokeSecret removes the cryptographically random secret that
|
||||
// corresponds to the given hash. This acts as a NOP if the secret does
|
||||
// not exist.
|
||||
RevokeSecret(context.Context, [sha256.Size]byte) error
|
||||
}
|
||||
|
||||
// ServiceLimiter abstracts the source of caveats that should be applied to an
|
||||
// LSAT for a particular service.
|
||||
type ServiceLimiter interface {
|
||||
// ServiceCapabilities returns the capabilities caveats for each
|
||||
// service. This determines which capabilities of each service can be
|
||||
// accessed.
|
||||
ServiceCapabilities(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
|
||||
|
||||
// ServiceConstraints returns the constraints for each service. This
|
||||
// enforces additional constraints on a particular service/service
|
||||
// capability.
|
||||
ServiceConstraints(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
|
||||
}
|
||||
|
||||
// Config packages all of the required dependencies to instantiate a new LSAT
|
||||
// mint.
|
||||
type Config struct {
|
||||
// Secrets is our source for LSAT secrets which will be used for
|
||||
// verification purposes.
|
||||
Secrets SecretStore
|
||||
|
||||
// Challenger is our source of new challenges to present requesters of
|
||||
// an LSAT with.
|
||||
Challenger Challenger
|
||||
|
||||
// ServiceLimiter provides us with how we should limit a new LSAT based
|
||||
// on its target services.
|
||||
ServiceLimiter ServiceLimiter
|
||||
}
|
||||
|
||||
// Mint is an entity that is able to mint and verify LSATs for a set of
|
||||
// services.
|
||||
type Mint struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
// New creates a new LSAT mint backed by its given dependencies.
|
||||
func New(cfg *Config) *Mint {
|
||||
return &Mint{cfg: *cfg}
|
||||
}
|
||||
|
||||
// MintLSAT mints a new LSAT for the target services.
|
||||
func (m *Mint) MintLSAT(ctx context.Context,
|
||||
services ...lsat.Service) (*macaroon.Macaroon, string, error) {
|
||||
|
||||
// We'll start by retrieving a new challenge in the form of a Lightning
|
||||
// payment request to present the requester of the LSAT with.
|
||||
paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// TODO(wilmer): remove invoice if any of the operations below fail?
|
||||
|
||||
// We can then proceed to mint the LSAT with a unique identifier that is
|
||||
// mapped to a unique secret.
|
||||
id, err := createUniqueIdentifier(paymentHash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
idHash := sha256.Sum256(id)
|
||||
secret, err := m.cfg.Secrets.NewSecret(ctx, idHash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
macaroon, err := macaroon.New(
|
||||
secret[:], id, "lsat", macaroon.LatestVersion,
|
||||
)
|
||||
if err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Include any restrictions that should be immediately applied to the
|
||||
// LSAT.
|
||||
var caveats []lsat.Caveat
|
||||
if len(services) > 0 {
|
||||
var err error
|
||||
caveats, err = m.caveatsForServices(ctx, services...)
|
||||
if err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
if err := lsat.AddFirstPartyCaveats(macaroon, caveats...); err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return macaroon, paymentRequest, nil
|
||||
}
|
||||
|
||||
// createUniqueIdentifier creates a new LSAT identifier bound to a payment hash
|
||||
// and a randomly generated ID.
|
||||
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {
|
||||
tokenID, err := generateTokenID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := &lsat.Identifier{
|
||||
Version: lsat.LatestVersion,
|
||||
PaymentHash: paymentHash,
|
||||
TokenID: tokenID,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := lsat.EncodeIdentifier(&buf, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateTokenID generates a new random LSAT ID.
|
||||
func generateTokenID() ([lsat.TokenIDSize]byte, error) {
|
||||
var tokenID [lsat.TokenIDSize]byte
|
||||
_, err := rand.Read(tokenID[:])
|
||||
return tokenID, err
|
||||
}
|
||||
|
||||
// caveatsForServices returns all of the caveats that should be applied to an
|
||||
// LSAT for the target services.
|
||||
func (m *Mint) caveatsForServices(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
servicesCaveat, err := lsat.NewServicesCaveat(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
capabilities, err := m.cfg.ServiceLimiter.ServiceCapabilities(
|
||||
ctx, services...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
constraints, err := m.cfg.ServiceLimiter.ServiceConstraints(
|
||||
ctx, services...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caveats := []lsat.Caveat{servicesCaveat}
|
||||
caveats = append(caveats, capabilities...)
|
||||
caveats = append(caveats, constraints...)
|
||||
return caveats, nil
|
||||
}
|
||||
|
||||
// VerificationParams holds all of the requirements to properly verify an LSAT.
|
||||
type VerificationParams struct {
|
||||
// Macaroon is the macaroon as part of the LSAT we'll attempt to verify.
|
||||
Macaroon *macaroon.Macaroon
|
||||
|
||||
// Preimage is the preimage that should correspond to the LSAT's payment
|
||||
// hash.
|
||||
Preimage lntypes.Preimage
|
||||
|
||||
// TargetService is the target service a user of an LSAT is attempting
|
||||
// to access.
|
||||
TargetService string
|
||||
}
|
||||
|
||||
// VerifyLSAT attempts to verify an LSAT with the given parameters.
|
||||
func (m *Mint) VerifyLSAT(ctx context.Context, params *VerificationParams) error {
|
||||
// We'll first perform a quick check to determine if a valid preimage
|
||||
// was provided.
|
||||
id, err := lsat.DecodeIdentifier(bytes.NewReader(params.Macaroon.Id()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if params.Preimage.Hash() != id.PaymentHash {
|
||||
return fmt.Errorf("invalid preimage %v for %v", params.Preimage,
|
||||
id.PaymentHash)
|
||||
}
|
||||
|
||||
// If there was, then we'll ensure the LSAT was minted by us.
|
||||
secret, err := m.cfg.Secrets.GetSecret(
|
||||
ctx, sha256.Sum256(params.Macaroon.Id()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawCaveats, err := params.Macaroon.VerifySignature(secret[:], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With the LSAT verified, we'll now inspect its caveats to ensure the
|
||||
// target service is authorized.
|
||||
var caveats []lsat.Caveat
|
||||
for _, rawCaveat := range rawCaveats {
|
||||
// LSATs can contain third-party caveats that we're not aware
|
||||
// of, so just skip those.
|
||||
caveat, err := lsat.DecodeCaveat(rawCaveat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
caveats = append(caveats, caveat)
|
||||
}
|
||||
return lsat.VerifyCaveats(
|
||||
caveats, lsat.NewServicesSatisfier(params.TargetService),
|
||||
)
|
||||
}
|
||||
227
mint/mint_test.go
Normal file
227
mint/mint_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
testService = lsat.Service{
|
||||
Name: "lightning_loop",
|
||||
Tier: lsat.BaseTier,
|
||||
}
|
||||
)
|
||||
|
||||
// TestBasicLSAT ensures that an LSAT can only access the services it's
|
||||
// authorized to.
|
||||
func TestBasicLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint a basic LSAT which is only able to access the given service.
|
||||
macaroon, _, err := mint.MintLSAT(ctx, testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
params := VerificationParams{
|
||||
Macaroon: macaroon,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, ¶ms); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should not be able to access an unknown service.
|
||||
unknownParams := params
|
||||
unknownParams.TargetService = "uknown"
|
||||
err = mint.VerifyLSAT(ctx, &unknownParams)
|
||||
if !strings.Contains(err.Error(), "not authorized") {
|
||||
t.Fatal("expected LSAT to not be authorized")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminLSAT ensures that an admin LSAT (one without a services caveat) is
|
||||
// authorized to access any service.
|
||||
func TestAdminLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint an admin LSAT by not including any services.
|
||||
macaroon, _, err := mint.MintLSAT(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should be able to access any service as it doesn't have a services
|
||||
// caveat.
|
||||
params := &VerificationParams{
|
||||
Macaroon: macaroon,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRevokedLSAT ensures that we can no longer verify a revoked LSAT.
|
||||
func TestRevokedLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint an LSAT and verify it.
|
||||
lsat, _, err := mint.MintLSAT(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
params := &VerificationParams{
|
||||
Macaroon: lsat,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Proceed to revoke it. We should no longer be able to verify it after.
|
||||
idHash := sha256.Sum256(lsat.Id())
|
||||
if err := mint.cfg.Secrets.RevokeSecret(ctx, idHash); err != nil {
|
||||
t.Fatalf("unable to revoke LSAT: %v", err)
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != ErrSecretNotFound {
|
||||
t.Fatalf("expected ErrSecretNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTamperedLSAT ensures that an LSAT that has been tampered with by
|
||||
// modifying its signature results in its verification failing.
|
||||
func TestTamperedLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint a new LSAT and verify it is valid.
|
||||
mac, _, err := mint.MintLSAT(ctx, testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
params := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, ¶ms); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Create a tampered LSAT from the valid one.
|
||||
macBytes, err := mac.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to serialize macaroon: %v", err)
|
||||
}
|
||||
macBytes[len(macBytes)-1] = 0x00
|
||||
var tampered macaroon.Macaroon
|
||||
if err := tampered.UnmarshalBinary(macBytes); err != nil {
|
||||
t.Fatalf("unable to deserialize macaroon: %v", err)
|
||||
}
|
||||
|
||||
// Attempting to verify the tampered LSAT should fail.
|
||||
tamperedParams := params
|
||||
tamperedParams.Macaroon = &tampered
|
||||
err = mint.VerifyLSAT(ctx, &tamperedParams)
|
||||
if !strings.Contains(err.Error(), "signature mismatch") {
|
||||
t.Fatal("expected tampered LSAT to be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDemotedServicesLSAT ensures that an LSAT which originally was authorized
|
||||
// to access a service, but was then demoted to no longer be the case, is no
|
||||
// longer authorized.
|
||||
func TestDemotedServicesLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
unauthorizedService := testService
|
||||
unauthorizedService.Name = "unauthorized"
|
||||
|
||||
// Mint an LSAT that is able to access two services, one of which will
|
||||
// be denied later on.
|
||||
mac, _, err := mint.MintLSAT(ctx, testService, unauthorizedService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should be able to access both services.
|
||||
authorizedParams := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, &authorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
unauthorizedParams := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: unauthorizedService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, &unauthorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Demote the second service by including an additional services caveat
|
||||
// that only includes the first service.
|
||||
services, err := lsat.NewServicesCaveat(testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create services caveat: %v", err)
|
||||
}
|
||||
err = lsat.AddFirstPartyCaveats(mac, services)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to demote LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should now only be able to access the first, but not the second.
|
||||
if err := mint.VerifyLSAT(ctx, &authorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
err = mint.VerifyLSAT(ctx, &unauthorizedParams)
|
||||
if !strings.Contains(err.Error(), "not authorized") {
|
||||
t.Fatal("expected macaroon to be invalid")
|
||||
}
|
||||
}
|
||||
113
mint/mock_test.go
Normal file
113
mint/mock_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"math/rand"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
var (
|
||||
testPreimage = lntypes.Preimage{
|
||||
0, 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,
|
||||
}
|
||||
testHash = testPreimage.Hash()
|
||||
testPayReq = "lnsb1..."
|
||||
)
|
||||
|
||||
type mockChallenger struct{}
|
||||
|
||||
var _ Challenger = (*mockChallenger)(nil)
|
||||
|
||||
func newMockChallenger() *mockChallenger {
|
||||
return &mockChallenger{}
|
||||
}
|
||||
|
||||
func (d *mockChallenger) NewChallenge() (string, lntypes.Hash, error) {
|
||||
return testPayReq, testHash, nil
|
||||
}
|
||||
|
||||
type mockSecretStore struct {
|
||||
secrets map[[sha256.Size]byte][lsat.SecretSize]byte
|
||||
}
|
||||
|
||||
var _ SecretStore = (*mockSecretStore)(nil)
|
||||
|
||||
func (s *mockSecretStore) NewSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
var secret [lsat.SecretSize]byte
|
||||
if _, err := rand.Read(secret[:]); err != nil {
|
||||
return secret, err
|
||||
}
|
||||
s.secrets[id] = secret
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (s *mockSecretStore) GetSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
secret, ok := s.secrets[id]
|
||||
if !ok {
|
||||
return secret, ErrSecretNotFound
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (s *mockSecretStore) RevokeSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) error {
|
||||
|
||||
delete(s.secrets, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMockSecretStore() *mockSecretStore {
|
||||
return &mockSecretStore{
|
||||
secrets: make(map[[sha256.Size]byte][lsat.SecretSize]byte),
|
||||
}
|
||||
}
|
||||
|
||||
type mockServiceLimiter struct {
|
||||
capabilities map[lsat.Service]lsat.Caveat
|
||||
constraints map[lsat.Service][]lsat.Caveat
|
||||
}
|
||||
|
||||
var _ ServiceLimiter = (*mockServiceLimiter)(nil)
|
||||
|
||||
func newMockServiceLimiter() *mockServiceLimiter {
|
||||
return &mockServiceLimiter{
|
||||
capabilities: make(map[lsat.Service]lsat.Caveat),
|
||||
constraints: make(map[lsat.Service][]lsat.Caveat),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *mockServiceLimiter) ServiceCapabilities(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
capabilities, ok := l.capabilities[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, capabilities)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (l *mockServiceLimiter) ServiceConstraints(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
constraints, ok := l.constraints[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, constraints...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -93,15 +93,15 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// accordingly.
|
||||
switch {
|
||||
case target.Auth.IsOn():
|
||||
if !p.authenticator.Accept(&r.Header) {
|
||||
if !p.authenticator.Accept(&r.Header, target.Name) {
|
||||
prefixLog.Infof("Authentication failed. Sending 402.")
|
||||
p.handlePaymentRequired(w, r)
|
||||
p.handlePaymentRequired(w, r, target.Name)
|
||||
return
|
||||
}
|
||||
case target.Auth.IsFreebie():
|
||||
// We only need to respect the freebie counter if the user
|
||||
// is not authenticated at all.
|
||||
if !p.authenticator.Accept(&r.Header) {
|
||||
if !p.authenticator.Accept(&r.Header, target.Name) {
|
||||
ok, err := target.freebieDb.CanPass(r, remoteIp)
|
||||
if err != nil {
|
||||
prefixLog.Errorf("Error querying freebie db: "+
|
||||
@@ -113,7 +113,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
p.handlePaymentRequired(w, r)
|
||||
p.handlePaymentRequired(w, r, target.Name)
|
||||
return
|
||||
}
|
||||
_, err = target.freebieDb.TallyFreebie(r, remoteIp)
|
||||
@@ -278,10 +278,12 @@ func addCorsHeaders(header http.Header) {
|
||||
|
||||
// handlePaymentRequired returns fresh challenge header fields and status code
|
||||
// to the client signaling that a payment is required to fulfil the request.
|
||||
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request) {
|
||||
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request,
|
||||
serviceName string) {
|
||||
|
||||
addCorsHeaders(r.Header)
|
||||
|
||||
header, err := p.authenticator.FreshChallengeHeader(r)
|
||||
header, err := p.authenticator.FreshChallengeHeader(r, serviceName)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating new challenge header: %v", err)
|
||||
sendDirectResponse(
|
||||
|
||||
@@ -20,6 +20,9 @@ var (
|
||||
// Service generically specifies configuration data for backend services to the
|
||||
// Kirin proxy.
|
||||
type Service struct {
|
||||
// Name is the name of the LSAT-enabled service.
|
||||
Name string `long:"name" description:"Name of the LSAT-enabled service"`
|
||||
|
||||
// TLSCertPath is the optional path to the service's TLS certificate.
|
||||
TLSCertPath string `long:"tlscertpath" description:"Path to the service's TLS certificate"`
|
||||
|
||||
@@ -55,6 +58,15 @@ type Service struct {
|
||||
// the file is sent encoded as base64.
|
||||
Headers map[string]string `long:"headers" description:"Header fields to always pass to the service"`
|
||||
|
||||
// Capabilities is the list of capabilities authorized for the service
|
||||
// at the base tier.
|
||||
Capabilities string `long:"capabilities" description:"A comma-separated list of the service capabilities authorized for the base tier"`
|
||||
|
||||
// Constraints is the set of constraints that will take form of caveats.
|
||||
// They'll be enforced for a service at the base tier. The key should
|
||||
// correspond to the caveat's condition.
|
||||
Constraints map[string]string `long:"constraints" description:"The service constraints to enforce at the base tier"`
|
||||
|
||||
freebieDb freebie.DB
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,17 @@ listenaddr: "localhost:8081"
|
||||
staticroot: "./static"
|
||||
debuglevel: "debug"
|
||||
|
||||
authenticator:
|
||||
lndhost: "localhost:10009"
|
||||
tlspath: "/path/to/lnd/tls.cert"
|
||||
macdir: "/path/to/lnd/data/chain/bitcoin/simnet"
|
||||
network: "simnet"
|
||||
|
||||
etcd:
|
||||
host: "localhost:2379"
|
||||
user: "user"
|
||||
password: "password"
|
||||
|
||||
services:
|
||||
# List of services that should be reachable behind the proxy.
|
||||
# Requests will be matched to the services in order, picking the first
|
||||
@@ -10,13 +21,18 @@ services:
|
||||
#
|
||||
# Use single quotes for regular expressions with special characters in them to
|
||||
# avoid YAML parsing errors!
|
||||
- hostregexp: '^service1.com$'
|
||||
- name: "service1"
|
||||
hostregexp: '^service1.com$'
|
||||
pathregexp: '^/.*$'
|
||||
address: "127.0.0.1:10009"
|
||||
protocol: https
|
||||
tlscertpath: "path-to-optional-tls-cert/tls.cert"
|
||||
capabilities: "add,subtract"
|
||||
|
||||
- hostregexp: "service2.com:8083"
|
||||
- name: "service2"
|
||||
hostregexp: "service2.com:8083"
|
||||
pathregexp: '^/.*$'
|
||||
address: "123.456.789:8082"
|
||||
protocol: https
|
||||
constraints:
|
||||
"valid_until": "2020-01-01"
|
||||
|
||||
92
secrets.go
Normal file
92
secrets.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package kirin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
)
|
||||
|
||||
var (
|
||||
// secretsPrefix is the key we'll use to prefix all LSAT identifiers
|
||||
// with when storing secrets in an etcd cluster.
|
||||
secretsPrefix = "secrets"
|
||||
)
|
||||
|
||||
// idKey returns the full key to store in the database for an LSAT identifier.
|
||||
// The identifier is hex-encoded in order to prevent conflicts with the etcd key
|
||||
// delimeter.
|
||||
//
|
||||
// The resulting path of the identifier bff4ee83 within etcd would look like:
|
||||
// lsat/proxy/secrets/bff4ee83
|
||||
func idKey(id [sha256.Size]byte) string {
|
||||
return strings.Join(
|
||||
[]string{topLevelKey, secretsPrefix, hex.EncodeToString(id[:])},
|
||||
etcdKeyDelimeter,
|
||||
)
|
||||
}
|
||||
|
||||
// secretStore is a store of LSAT secrets backed by an etcd cluster.
|
||||
type secretStore struct {
|
||||
*clientv3.Client
|
||||
}
|
||||
|
||||
// A compile-time constraint to ensure secretStore implements mint.SecretStore.
|
||||
var _ mint.SecretStore = (*secretStore)(nil)
|
||||
|
||||
// newSecretStore instantiates a new LSAT secrets store backed by an etcd
|
||||
// cluster.
|
||||
func newSecretStore(client *clientv3.Client) *secretStore {
|
||||
return &secretStore{Client: client}
|
||||
}
|
||||
|
||||
// NewSecret creates a new cryptographically random secret which is keyed by the
|
||||
// given hash.
|
||||
func (s *secretStore) NewSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
var secret [lsat.SecretSize]byte
|
||||
if _, err := rand.Read(secret[:]); err != nil {
|
||||
return secret, err
|
||||
}
|
||||
|
||||
_, err := s.Put(ctx, idKey(id), string(secret[:]))
|
||||
return secret, err
|
||||
}
|
||||
|
||||
// GetSecret returns the cryptographically random secret that corresponds to the
|
||||
// given hash. If there is no secret, then mint.ErrSecretNotFound is returned.
|
||||
func (s *secretStore) GetSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
resp, err := s.Get(ctx, idKey(id))
|
||||
if err != nil {
|
||||
return [lsat.SecretSize]byte{}, err
|
||||
}
|
||||
if len(resp.Kvs) == 0 {
|
||||
return [lsat.SecretSize]byte{}, mint.ErrSecretNotFound
|
||||
}
|
||||
if len(resp.Kvs[0].Value) != lsat.SecretSize {
|
||||
return [lsat.SecretSize]byte{}, fmt.Errorf("invalid secret "+
|
||||
"size %v", len(resp.Kvs[0].Value))
|
||||
}
|
||||
|
||||
var secret [lsat.SecretSize]byte
|
||||
copy(secret[:], resp.Kvs[0].Value)
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// RevokeSecret removes the cryptographically random secret that corresponds to
|
||||
// the given hash. This acts as a NOP if the secret does not exist.
|
||||
func (s *secretStore) RevokeSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) error {
|
||||
|
||||
_, err := s.Delete(ctx, idKey(id))
|
||||
return err
|
||||
}
|
||||
115
secrets_test.go
Normal file
115
secrets_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package kirin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/embed"
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
)
|
||||
|
||||
// etcdSetup is a helper that instantiates a new etcd cluster along with a
|
||||
// client connection to it. A cleanup closure is also returned to free any
|
||||
// allocated resources required by etcd.
|
||||
func etcdSetup(t *testing.T) (*clientv3.Client, func()) {
|
||||
t.Helper()
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "etcd")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
cfg.Dir = tempDir
|
||||
cfg.LCUrls = []url.URL{{Host: "127.0.0.1:9125"}}
|
||||
cfg.LPUrls = []url.URL{{Host: "127.0.0.1:9126"}}
|
||||
|
||||
etcd, err := embed.StartEtcd(cfg)
|
||||
if err != nil {
|
||||
os.RemoveAll(tempDir)
|
||||
t.Fatalf("unable to start etcd: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-etcd.Server.ReadyNotify():
|
||||
case <-time.After(5 * time.Second):
|
||||
os.RemoveAll(tempDir)
|
||||
etcd.Server.Stop() // trigger a shutdown
|
||||
t.Fatal("server took too long to start")
|
||||
}
|
||||
|
||||
client, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: []string{cfg.LCUrls[0].Host},
|
||||
DialTimeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect to etcd: %v", err)
|
||||
}
|
||||
|
||||
return client, func() {
|
||||
etcd.Close()
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
// assertSecretExists is a helper to determine if a secret for the given
|
||||
// identifier exists in the store. If it exists, its value is compared against
|
||||
// the expected secret.
|
||||
func assertSecretExists(t *testing.T, store *secretStore, id [sha256.Size]byte,
|
||||
expSecret *[lsat.SecretSize]byte) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
exists := expSecret != nil
|
||||
secret, err := store.GetSecret(context.Background(), id)
|
||||
switch {
|
||||
case exists && err != nil:
|
||||
t.Fatalf("unable to retrieve secret: %v", err)
|
||||
case !exists && err != mint.ErrSecretNotFound:
|
||||
t.Fatalf("expected error ErrSecretNotFound, got \"%v\"", err)
|
||||
case exists:
|
||||
if secret != *expSecret {
|
||||
t.Fatalf("expected secret %x, got %x", expSecret, secret)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestSecretStore ensures the different operations of the secretStore behave as
|
||||
// expected.
|
||||
func TestSecretStore(t *testing.T) {
|
||||
etcdClient, serverCleanup := etcdSetup(t)
|
||||
defer etcdClient.Close()
|
||||
defer serverCleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
store := newSecretStore(etcdClient)
|
||||
|
||||
// Create a test ID and ensure a secret doesn't exist for it yet as we
|
||||
// haven't created one.
|
||||
var id [sha256.Size]byte
|
||||
copy(id[:], bytes.Repeat([]byte("A"), 32))
|
||||
assertSecretExists(t, store, id, nil)
|
||||
|
||||
// Create one and ensure we can retrieve it at a later point.
|
||||
secret, err := store.NewSecret(ctx, id)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate new secret: %v", err)
|
||||
}
|
||||
assertSecretExists(t, store, id, &secret)
|
||||
|
||||
// Once revoked, it should no longer exist.
|
||||
if err := store.RevokeSecret(ctx, id); err != nil {
|
||||
t.Fatalf("unable to revoke secret: %v", err)
|
||||
}
|
||||
assertSecretExists(t, store, id, nil)
|
||||
}
|
||||
78
services.go
Normal file
78
services.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package kirin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/lightninglabs/kirin/mint"
|
||||
"github.com/lightninglabs/kirin/proxy"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
)
|
||||
|
||||
// staticServiceLimiter provides static restrictions for services.
|
||||
//
|
||||
// TODO(wilmer): use etcd instead.
|
||||
type staticServiceLimiter struct {
|
||||
capabilities map[lsat.Service]lsat.Caveat
|
||||
constraints map[lsat.Service][]lsat.Caveat
|
||||
}
|
||||
|
||||
// A compile-time constraint to ensure staticServiceLimiter implements
|
||||
// mint.ServiceLimiter.
|
||||
var _ mint.ServiceLimiter = (*staticServiceLimiter)(nil)
|
||||
|
||||
// newStaticServiceLimiter instantiates a new static service limiter backed by
|
||||
// the given restrictions.
|
||||
func newStaticServiceLimiter(proxyServices []*proxy.Service) *staticServiceLimiter {
|
||||
capabilities := make(map[lsat.Service]lsat.Caveat)
|
||||
constraints := make(map[lsat.Service][]lsat.Caveat)
|
||||
|
||||
for _, proxyService := range proxyServices {
|
||||
s := lsat.Service{Name: proxyService.Name, Tier: lsat.BaseTier}
|
||||
capabilities[s] = lsat.NewCapabilitiesCaveat(
|
||||
proxyService.Name, proxyService.Capabilities,
|
||||
)
|
||||
for cond, value := range proxyService.Constraints {
|
||||
caveat := lsat.Caveat{Condition: cond, Value: value}
|
||||
constraints[s] = append(constraints[s], caveat)
|
||||
}
|
||||
}
|
||||
|
||||
return &staticServiceLimiter{
|
||||
capabilities: capabilities,
|
||||
constraints: constraints,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceCapabilities returns the capabilities caveats for each service. This
|
||||
// determines which capabilities of each service can be accessed.
|
||||
func (l *staticServiceLimiter) ServiceCapabilities(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
capabilities, ok := l.capabilities[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, capabilities)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ServiceConstraints returns the constraints for each service. This enforces
|
||||
// additional constraints on a particular service/service capability.
|
||||
func (l *staticServiceLimiter) ServiceConstraints(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
constraints, ok := l.constraints[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, constraints...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user