diff --git a/auth/authenticator.go b/auth/authenticator.go index e7d2123..3ba72f9 100644 --- a/auth/authenticator.go +++ b/auth/authenticator.go @@ -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: // 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 diff --git a/auth/authenticator_test.go b/auth/authenticator_test.go index 83057bd..4467892 100644 --- a/auth/authenticator_test.go +++ b/auth/authenticator_test.go @@ -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) diff --git a/auth/interface.go b/auth/interface.go index 00fa8ad..600a114 100644 --- a/auth/interface.go +++ b/auth/interface.go @@ -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 } diff --git a/auth/mock_authenticator.go b/auth/mock_authenticator.go index e854bb1..487cb9f 100644 --- a/auth/mock_authenticator.go +++ b/auth/mock_authenticator.go @@ -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 diff --git a/auth/mock_test.go b/auth/mock_test.go new file mode 100644 index 0000000..cde61c9 --- /dev/null +++ b/auth/mock_test.go @@ -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 +} diff --git a/challenger.go b/challenger.go index 4c79a62..97cfc50 100644 --- a/challenger.go +++ b/challenger.go @@ -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") diff --git a/config.go b/config.go index 368b57f..83031c9 100644 --- a/config.go +++ b/config.go @@ -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 diff --git a/go.mod b/go.mod index 1db81df..5a8b387 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 539edfd..56185ee 100644 --- a/go.sum +++ b/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= diff --git a/kirin.go b/kirin.go index 5b7459c..25cd175 100644 --- a/kirin.go +++ b/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) diff --git a/macaroons/service.go b/macaroons/service.go deleted file mode 100644 index a102820..0000000 --- a/macaroons/service.go +++ /dev/null @@ -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 -} diff --git a/mint/mint.go b/mint/mint.go new file mode 100644 index 0000000..6089c06 --- /dev/null +++ b/mint/mint.go @@ -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), + ) +} diff --git a/mint/mint_test.go b/mint/mint_test.go new file mode 100644 index 0000000..dd0b9af --- /dev/null +++ b/mint/mint_test.go @@ -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") + } +} diff --git a/mint/mock_test.go b/mint/mock_test.go new file mode 100644 index 0000000..7dddc18 --- /dev/null +++ b/mint/mock_test.go @@ -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 +} diff --git a/proxy/proxy.go b/proxy/proxy.go index c1c30be..2a7f057 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -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( diff --git a/proxy/service.go b/proxy/service.go index e74218a..ec38b26 100644 --- a/proxy/service.go +++ b/proxy/service.go @@ -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 } diff --git a/sample-conf.yaml b/sample-conf.yaml index bcf9cd0..adfa26b 100644 --- a/sample-conf.yaml +++ b/sample-conf.yaml @@ -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" diff --git a/secrets.go b/secrets.go new file mode 100644 index 0000000..194d84d --- /dev/null +++ b/secrets.go @@ -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 +} diff --git a/secrets_test.go b/secrets_test.go new file mode 100644 index 0000000..af93bb6 --- /dev/null +++ b/secrets_test.go @@ -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) +} diff --git a/services.go b/services.go new file mode 100644 index 0000000..8e068c1 --- /dev/null +++ b/services.go @@ -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 +}