From 156199090adfe6d1ae05163d58e0541cf7ef031f Mon Sep 17 00:00:00 2001 From: Orbital Date: Sun, 4 Sep 2022 21:59:52 -0500 Subject: [PATCH 1/5] multi: move challenger and secrets files to new packages Moves challenger and secrets files to new packages so they can be imported elsewhere. --- aperture.go | 27 +++++---- challenger.go => challenger/challenger.go | 21 +++++-- .../challenger_test.go | 2 +- challenger/log.go | 31 ++++++++++ log.go | 4 ++ onion_store.go | 4 +- onion_store_test.go | 3 +- secrets.go => secrets/secrets.go | 36 +++++++---- secrets_test.go => secrets/secrets_test.go | 60 ++----------------- secrets/test_util.go | 56 +++++++++++++++++ 10 files changed, 156 insertions(+), 88 deletions(-) rename challenger.go => challenger/challenger.go (93%) rename challenger_test.go => challenger/challenger_test.go (99%) create mode 100644 challenger/log.go rename secrets.go => secrets/secrets.go (68%) rename secrets_test.go => secrets/secrets_test.go (51%) create mode 100644 secrets/test_util.go diff --git a/aperture.go b/aperture.go index a57b458..e5146db 100644 --- a/aperture.go +++ b/aperture.go @@ -18,8 +18,10 @@ import ( gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" flags "github.com/jessevdk/go-flags" "github.com/lightninglabs/aperture/auth" + "github.com/lightninglabs/aperture/challenger" "github.com/lightninglabs/aperture/mint" "github.com/lightninglabs/aperture/proxy" + "github.com/lightninglabs/aperture/secrets" "github.com/lightninglabs/lightning-node-connect/hashmailrpc" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" @@ -42,14 +44,6 @@ import ( ) 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 = "/" - // selfSignedCertOrganization is the static string that we encode in the // organization field of a certificate if we create it ourselves. selfSignedCertOrganization = "aperture autogenerated cert" @@ -160,7 +154,7 @@ type Aperture struct { cfg *Config etcdClient *clientv3.Client - challenger *LndChallenger + challenger *challenger.LndChallenger httpsServer *http.Server torHTTPServer *http.Server proxy *proxy.Proxy @@ -229,8 +223,15 @@ func (a *Aperture) Start(errChan chan error) error { } if !a.cfg.Authenticator.Disable { - a.challenger, err = NewLndChallenger( - a.cfg.Authenticator, genInvoiceReq, errChan, + challengerAuth := &challenger.AuthConfig{ + LndHost: a.cfg.Authenticator.LndHost, + TLSPath: a.cfg.Authenticator.TLSPath, + MacDir: a.cfg.Authenticator.MacDir, + Network: a.cfg.Authenticator.Network, + Disable: a.cfg.Authenticator.Disable, + } + a.challenger, err = challenger.NewLndChallenger( + challengerAuth, genInvoiceReq, errChan, ) if err != nil { return err @@ -655,12 +656,12 @@ func initTorListener(cfg *Config, etcd *clientv3.Client) (*tor.Controller, error } // createProxy creates the proxy with all the services it needs. -func createProxy(cfg *Config, challenger *LndChallenger, +func createProxy(cfg *Config, challenger *challenger.LndChallenger, etcdClient *clientv3.Client) (*proxy.Proxy, func(), error) { minter := mint.New(&mint.Config{ Challenger: challenger, - Secrets: newSecretStore(etcdClient), + Secrets: secrets.NewStore(etcdClient), ServiceLimiter: newStaticServiceLimiter(cfg.Services), Now: time.Now, }) diff --git a/challenger.go b/challenger/challenger.go similarity index 93% rename from challenger.go rename to challenger/challenger.go index b1690eb..f0b8dcf 100644 --- a/challenger.go +++ b/challenger/challenger.go @@ -1,4 +1,4 @@ -package aperture +package challenger import ( "context" @@ -55,15 +55,28 @@ type LndChallenger struct { wg sync.WaitGroup } +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"` + + TLSPath string `long:"tlspath" description:"Path to LND instance's tls certificate"` + + MacDir string `long:"macdir" description:"Directory containing LND instance's macaroons"` + + Network string `long:"network" description:"The network LND is connected to." choice:"regtest" choice:"simnet" choice:"testnet" choice:"mainnet"` + + Disable bool `long:"disable" description:"Whether to disable LND auth."` +} + // A compile time flag to ensure the LndChallenger satisfies the // mint.Challenger and auth.InvoiceChecker interface. var _ mint.Challenger = (*LndChallenger)(nil) var _ auth.InvoiceChecker = (*LndChallenger)(nil) const ( - // invoiceMacaroonName is the name of the invoice macaroon belonging + // InvoiceMacaroonName is the name of the invoice macaroon belonging // to the target lnd node. - invoiceMacaroonName = "invoice.macaroon" + InvoiceMacaroonName = "invoice.macaroon" ) // NewLndChallenger creates a new challenger that uses the given connection @@ -77,7 +90,7 @@ func NewLndChallenger(cfg *AuthConfig, genInvoiceReq InvoiceRequestGenerator, client, err := lndclient.NewBasicClient( cfg.LndHost, cfg.TLSPath, cfg.MacDir, cfg.Network, - lndclient.MacFilename(invoiceMacaroonName), + lndclient.MacFilename(InvoiceMacaroonName), ) if err != nil { return nil, err diff --git a/challenger_test.go b/challenger/challenger_test.go similarity index 99% rename from challenger_test.go rename to challenger/challenger_test.go index f0bf7e0..185a65f 100644 --- a/challenger_test.go +++ b/challenger/challenger_test.go @@ -1,4 +1,4 @@ -package aperture +package challenger import ( "context" diff --git a/challenger/log.go b/challenger/log.go new file mode 100644 index 0000000..9980e64 --- /dev/null +++ b/challenger/log.go @@ -0,0 +1,31 @@ +package challenger + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "CHAL" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/log.go b/log.go index 8d375c3..6111bb6 100644 --- a/log.go +++ b/log.go @@ -3,6 +3,7 @@ package aperture import ( "github.com/btcsuite/btclog" "github.com/lightninglabs/aperture/auth" + "github.com/lightninglabs/aperture/challenger" "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/proxy" "github.com/lightninglabs/lndclient" @@ -27,6 +28,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { lnd.SetSubLogger(root, Subsystem, log) lnd.AddSubLogger(root, auth.Subsystem, intercept, auth.UseLogger) + lnd.AddSubLogger( + root, challenger.Subsystem, intercept, challenger.UseLogger, + ) lnd.AddSubLogger(root, lsat.Subsystem, intercept, lsat.UseLogger) lnd.AddSubLogger(root, proxy.Subsystem, intercept, proxy.UseLogger) lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger) diff --git a/onion_store.go b/onion_store.go index 9db77a9..5affe6d 100644 --- a/onion_store.go +++ b/onion_store.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/lightninglabs/aperture/secrets" "github.com/lightningnetwork/lnd/tor" clientv3 "go.etcd.io/etcd/client/v3" ) @@ -20,7 +21,8 @@ const ( // onionPath is the full path to an onion service's private key. var onionPath = strings.Join( - []string{topLevelKey, onionDir, onionV3Dir}, etcdKeyDelimeter, + []string{secrets.TopLevelKey, onionDir, onionV3Dir}, + secrets.EtcdKeyDelimeter, ) // onionStore is an etcd-based implementation of tor.OnionStore. diff --git a/onion_store_test.go b/onion_store_test.go index 4d26c34..28f1aea 100644 --- a/onion_store_test.go +++ b/onion_store_test.go @@ -4,6 +4,7 @@ import ( "bytes" "testing" + "github.com/lightninglabs/aperture/secrets" "github.com/lightningnetwork/lnd/tor" ) @@ -35,7 +36,7 @@ func assertPrivateKeyExists(t *testing.T, store *onionStore, // TestOnionStore ensures the different operations of the onionStore behave // as expected. func TestOnionStore(t *testing.T) { - etcdClient, serverCleanup := etcdSetup(t) + etcdClient, serverCleanup := secrets.EtcdSetup(t) defer etcdClient.Close() defer serverCleanup() diff --git a/secrets.go b/secrets/secrets.go similarity index 68% rename from secrets.go rename to secrets/secrets.go index e7eb3b3..07193a0 100644 --- a/secrets.go +++ b/secrets/secrets.go @@ -1,4 +1,4 @@ -package aperture +package secrets import ( "context" @@ -13,6 +13,16 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) +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 = "/" +) + var ( // secretsPrefix is the key we'll use to prefix all LSAT identifiers // with when storing secrets in an etcd cluster. @@ -27,28 +37,28 @@ var ( // lsat/proxy/secrets/bff4ee83 func idKey(id [sha256.Size]byte) string { return strings.Join( - []string{topLevelKey, secretsPrefix, hex.EncodeToString(id[:])}, - etcdKeyDelimeter, + []string{TopLevelKey, secretsPrefix, hex.EncodeToString(id[:])}, + EtcdKeyDelimeter, ) } -// secretStore is a store of LSAT secrets backed by an etcd cluster. -type secretStore struct { +// 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) +// 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 +// NewStore instantiates a new LSAT secrets store backed by an etcd // cluster. -func newSecretStore(client *clientv3.Client) *secretStore { - return &secretStore{Client: client} +func NewStore(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, +func (s *SecretStore) NewSecret(ctx context.Context, id [sha256.Size]byte) ([lsat.SecretSize]byte, error) { var secret [lsat.SecretSize]byte @@ -62,7 +72,7 @@ func (s *secretStore) NewSecret(ctx context.Context, // 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, +func (s *SecretStore) GetSecret(ctx context.Context, id [sha256.Size]byte) ([lsat.SecretSize]byte, error) { resp, err := s.Get(ctx, idKey(id)) @@ -84,7 +94,7 @@ func (s *secretStore) GetSecret(ctx context.Context, // 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, +func (s *SecretStore) RevokeSecret(ctx context.Context, id [sha256.Size]byte) error { _, err := s.Delete(ctx, idKey(id)) diff --git a/secrets_test.go b/secrets/secrets_test.go similarity index 51% rename from secrets_test.go rename to secrets/secrets_test.go index 3c213a2..0e6a4b6 100644 --- a/secrets_test.go +++ b/secrets/secrets_test.go @@ -1,69 +1,19 @@ -package aperture +package secrets import ( "bytes" "context" "crypto/sha256" - "net/url" - "os" "testing" - "time" "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/mint" - clientv3 "go.etcd.io/etcd/client/v3" - "go.etcd.io/etcd/server/v3/embed" ) -// 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 := os.MkdirTemp("", "etcd") - if err != nil { - t.Fatalf("unable to create temp dir: %v", err) - } - - cfg := embed.NewConfig() - cfg.Dir = tempDir - cfg.Logger = "zap" - 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, +func assertSecretExists(t *testing.T, store *SecretStore, id [sha256.Size]byte, expSecret *[lsat.SecretSize]byte) { t.Helper() @@ -84,15 +34,15 @@ func assertSecretExists(t *testing.T, store *secretStore, id [sha256.Size]byte, } } -// TestSecretStore ensures the different operations of the secretStore behave as +// TestSecretStore ensures the different operations of the SecretStore behave as // expected. func TestSecretStore(t *testing.T) { - etcdClient, serverCleanup := etcdSetup(t) + etcdClient, serverCleanup := EtcdSetup(t) defer etcdClient.Close() defer serverCleanup() ctx := context.Background() - store := newSecretStore(etcdClient) + store := NewStore(etcdClient) // Create a test ID and ensure a secret doesn't exist for it yet as we // haven't created one. diff --git a/secrets/test_util.go b/secrets/test_util.go new file mode 100644 index 0000000..43231c4 --- /dev/null +++ b/secrets/test_util.go @@ -0,0 +1,56 @@ +package secrets + +import ( + "net/url" + "os" + "testing" + "time" + + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/server/v3/embed" +) + +// 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 := os.MkdirTemp("", "etcd") + if err != nil { + t.Fatalf("unable to create temp dir: %v", err) + } + + cfg := embed.NewConfig() + cfg.Dir = tempDir + cfg.Logger = "zap" + 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) + } +} From e3d746b186b0e703fe9ff9fb99b094590a3ba7e9 Mon Sep 17 00:00:00 2001 From: Orbital Date: Wed, 24 May 2023 19:04:55 -0500 Subject: [PATCH 2/5] challenger: change NewLndChallenger client parameter - Alters NewLndChallenger so that it takes challenger.InvoiceClient in as a parameter. (This is useful in the case of using NewLndChallenger in the lnd watchtower project, to avoid having to import lndclient, which leads to an import cycle.) - Also deletes the AuthConfig parameter. Since we now pass the LND client directly into the function, the AuthConfig parameter is no longer needed. --- aperture.go | 19 ++++++++++++------- challenger/challenger.go | 26 ++------------------------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/aperture.go b/aperture.go index e5146db..71ab231 100644 --- a/aperture.go +++ b/aperture.go @@ -23,6 +23,7 @@ import ( "github.com/lightninglabs/aperture/proxy" "github.com/lightninglabs/aperture/secrets" "github.com/lightninglabs/lightning-node-connect/hashmailrpc" + "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/cert" @@ -223,15 +224,19 @@ func (a *Aperture) Start(errChan chan error) error { } if !a.cfg.Authenticator.Disable { - challengerAuth := &challenger.AuthConfig{ - LndHost: a.cfg.Authenticator.LndHost, - TLSPath: a.cfg.Authenticator.TLSPath, - MacDir: a.cfg.Authenticator.MacDir, - Network: a.cfg.Authenticator.Network, - Disable: a.cfg.Authenticator.Disable, + client, err := lndclient.NewBasicClient( + a.cfg.Authenticator.LndHost, + a.cfg.Authenticator.TLSPath, + a.cfg.Authenticator.MacDir, + a.cfg.Authenticator.Network, + lndclient.MacFilename(challenger.InvoiceMacaroonName), + ) + if err != nil { + return err } + a.challenger, err = challenger.NewLndChallenger( - challengerAuth, genInvoiceReq, errChan, + genInvoiceReq, client, errChan, ) if err != nil { return err diff --git a/challenger/challenger.go b/challenger/challenger.go index f0b8dcf..581daec 100644 --- a/challenger/challenger.go +++ b/challenger/challenger.go @@ -11,7 +11,6 @@ import ( "github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/mint" - "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "google.golang.org/grpc" @@ -55,19 +54,6 @@ type LndChallenger struct { wg sync.WaitGroup } -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"` - - TLSPath string `long:"tlspath" description:"Path to LND instance's tls certificate"` - - MacDir string `long:"macdir" description:"Directory containing LND instance's macaroons"` - - Network string `long:"network" description:"The network LND is connected to." choice:"regtest" choice:"simnet" choice:"testnet" choice:"mainnet"` - - Disable bool `long:"disable" description:"Whether to disable LND auth."` -} - // A compile time flag to ensure the LndChallenger satisfies the // mint.Challenger and auth.InvoiceChecker interface. var _ mint.Challenger = (*LndChallenger)(nil) @@ -81,21 +67,13 @@ const ( // 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, - errChan chan<- error) (*LndChallenger, error) { +func NewLndChallenger(genInvoiceReq InvoiceRequestGenerator, + client InvoiceClient, errChan chan<- error) (*LndChallenger, error) { if genInvoiceReq == nil { return nil, fmt.Errorf("genInvoiceReq cannot be nil") } - client, err := lndclient.NewBasicClient( - cfg.LndHost, cfg.TLSPath, cfg.MacDir, cfg.Network, - lndclient.MacFilename(InvoiceMacaroonName), - ) - if err != nil { - return nil, err - } - invoicesMtx := &sync.Mutex{} return &LndChallenger{ client: client, From e886fa297c4612e7866524cce356b32cc4f6b69a Mon Sep 17 00:00:00 2001 From: Turtle Date: Fri, 3 Mar 2023 21:46:07 -0600 Subject: [PATCH 3/5] auth: export mock mint so we can use it elsewhere --- auth/authenticator_test.go | 30 +++--------------- auth/mock_test.go | 40 ------------------------ auth/test_utils.go | 64 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 66 deletions(-) delete mode 100644 auth/mock_test.go create mode 100644 auth/test_utils.go diff --git a/auth/authenticator_test.go b/auth/authenticator_test.go index efd3629..e0f5bfc 100644 --- a/auth/authenticator_test.go +++ b/auth/authenticator_test.go @@ -9,37 +9,15 @@ import ( "github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/lsat" - "gopkg.in/macaroon.v2" ) -// createDummyMacHex creates a valid macaroon with dummy content for our tests. -func createDummyMacHex(preimage string) string { - dummyMac, err := macaroon.New( - []byte("aabbccddeeff00112233445566778899"), []byte("AA=="), - "aperture", macaroon.LatestVersion, - ) - if err != nil { - panic(err) - } - preimageCaveat := lsat.Caveat{Condition: lsat.PreimageKey, Value: preimage} - err = lsat.AddFirstPartyCaveats(dummyMac, preimageCaveat) - if err != nil { - panic(err) - } - macBytes, err := dummyMac.MarshalBinary() - if err != nil { - panic(err) - } - return hex.EncodeToString(macBytes) -} - // TestLsatAuthenticator tests that the authenticator properly handles auth // headers and the tokens contained in them. func TestLsatAuthenticator(t *testing.T) { var ( testPreimage = "49349dfea4abed3cd14f6d356afa83de" + "9787b609f088c8df09bacc7b4bd21b39" - testMacHex = createDummyMacHex(testPreimage) + testMacHex = auth.CreateDummyMacHex(testPreimage) testMacBytes, _ = hex.DecodeString(testMacHex) testMacBase64 = base64.StdEncoding.EncodeToString( testMacBytes, @@ -139,10 +117,10 @@ func TestLsatAuthenticator(t *testing.T) { } ) - c := &mockChecker{} - a := auth.NewLsatAuthenticator(&mockMint{}, c) + c := &auth.MockChecker{} + a := auth.NewLsatAuthenticator(&auth.MockMint{}, c) for _, testCase := range headerTests { - c.err = testCase.checkErr + c.Err = testCase.checkErr result := a.Accept(testCase.header, "test") if result != testCase.result { t.Fatalf("test case %s failed. got %v expected %v", diff --git a/auth/mock_test.go b/auth/mock_test.go deleted file mode 100644 index c2f92b1..0000000 --- a/auth/mock_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package auth_test - -import ( - "context" - "time" - - "github.com/lightninglabs/aperture/auth" - "github.com/lightninglabs/aperture/lsat" - "github.com/lightninglabs/aperture/mint" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntypes" - "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 -} - -type mockChecker struct { - err error -} - -var _ auth.InvoiceChecker = (*mockChecker)(nil) - -func (m *mockChecker) VerifyInvoiceStatus(lntypes.Hash, - lnrpc.Invoice_InvoiceState, time.Duration) error { - - return m.err -} diff --git a/auth/test_utils.go b/auth/test_utils.go new file mode 100644 index 0000000..3424bd9 --- /dev/null +++ b/auth/test_utils.go @@ -0,0 +1,64 @@ +package auth + +import ( + "context" + "encoding/hex" + "time" + + "github.com/lightninglabs/aperture/lsat" + "github.com/lightninglabs/aperture/mint" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntypes" + "gopkg.in/macaroon.v2" +) + +// CreateDummyMacHex creates a valid macaroon with dummy content for our tests. +func CreateDummyMacHex(preimage string) string { + dummyMac, err := macaroon.New( + []byte("aabbccddeeff00112233445566778899"), []byte("AA=="), + "aperture", macaroon.LatestVersion, + ) + if err != nil { + panic(err) + } + preimageCaveat := lsat.Caveat{ + Condition: lsat.PreimageKey, + Value: preimage, + } + err = lsat.AddFirstPartyCaveats(dummyMac, preimageCaveat) + if err != nil { + panic(err) + } + macBytes, err := dummyMac.MarshalBinary() + if err != nil { + panic(err) + } + return hex.EncodeToString(macBytes) +} + +type MockMint struct { +} + +var _ 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 +} + +type MockChecker struct { + Err error +} + +var _ InvoiceChecker = (*MockChecker)(nil) + +func (m *MockChecker) VerifyInvoiceStatus(lntypes.Hash, + lnrpc.Invoice_InvoiceState, time.Duration) error { + + return m.Err +} From fa45009fd86a7fc3f7c2271db8002efb4eaf7deb Mon Sep 17 00:00:00 2001 From: Turtle Date: Sat, 4 Mar 2023 15:59:50 -0600 Subject: [PATCH 4/5] challenger: export mock invoice client --- challenger/challenger_test.go | 90 +---------------------- challenger/mock_invoice_client.go | 118 ++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 88 deletions(-) create mode 100644 challenger/mock_invoice_client.go diff --git a/challenger/challenger_test.go b/challenger/challenger_test.go index 185a65f..eaf82aa 100644 --- a/challenger/challenger_test.go +++ b/challenger/challenger_test.go @@ -1,7 +1,6 @@ package challenger import ( - "context" "fmt" "sync" "testing" @@ -10,86 +9,14 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" - "google.golang.org/grpc" ) var ( defaultTimeout = 100 * time.Millisecond ) -type invoiceStreamMock struct { - lnrpc.Lightning_SubscribeInvoicesClient - - updateChan chan *lnrpc.Invoice - errChan chan error - quit chan struct{} -} - -func (i *invoiceStreamMock) Recv() (*lnrpc.Invoice, error) { - select { - case msg := <-i.updateChan: - return msg, nil - - case err := <-i.errChan: - return nil, err - - case <-i.quit: - return nil, context.Canceled - } -} - -type mockInvoiceClient struct { - invoices []*lnrpc.Invoice - updateChan chan *lnrpc.Invoice - errChan chan error - quit chan struct{} - - lastAddIndex uint64 -} - -// ListInvoices returns a paginated list of all invoices known to lnd. -func (m *mockInvoiceClient) ListInvoices(_ context.Context, - _ *lnrpc.ListInvoiceRequest, - _ ...grpc.CallOption) (*lnrpc.ListInvoiceResponse, error) { - - return &lnrpc.ListInvoiceResponse{ - Invoices: m.invoices, - }, nil -} - -// SubscribeInvoices subscribes to updates on invoices. -func (m *mockInvoiceClient) SubscribeInvoices(_ context.Context, - in *lnrpc.InvoiceSubscription, _ ...grpc.CallOption) ( - lnrpc.Lightning_SubscribeInvoicesClient, error) { - - m.lastAddIndex = in.AddIndex - - return &invoiceStreamMock{ - updateChan: m.updateChan, - errChan: m.errChan, - quit: m.quit, - }, nil -} - -// AddInvoice adds a new invoice to lnd. -func (m *mockInvoiceClient) AddInvoice(_ context.Context, in *lnrpc.Invoice, - _ ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { - - m.invoices = append(m.invoices, in) - - return &lnrpc.AddInvoiceResponse{ - RHash: in.RHash, - PaymentRequest: in.PaymentRequest, - AddIndex: uint64(len(m.invoices) - 1), - }, nil -} - -func (m *mockInvoiceClient) stop() { - close(m.quit) -} - -func newChallenger() (*LndChallenger, *mockInvoiceClient, chan error) { - mockClient := &mockInvoiceClient{ +func newChallenger() (*LndChallenger, *MockInvoiceClient, chan error) { + mockClient := &MockInvoiceClient{ updateChan: make(chan *lnrpc.Invoice), errChan: make(chan error, 1), quit: make(chan struct{}), @@ -111,19 +38,6 @@ func newChallenger() (*LndChallenger, *mockInvoiceClient, chan error) { }, mockClient, mainErrChan } -func newInvoice(hash lntypes.Hash, addIndex uint64, - state lnrpc.Invoice_InvoiceState) *lnrpc.Invoice { - - return &lnrpc.Invoice{ - PaymentRequest: "foo", - RHash: hash[:], - AddIndex: addIndex, - State: state, - CreationDate: time.Now().Unix(), - Expiry: 10, - } -} - func TestLndChallenger(t *testing.T) { t.Parallel() diff --git a/challenger/mock_invoice_client.go b/challenger/mock_invoice_client.go new file mode 100644 index 0000000..c60c6d6 --- /dev/null +++ b/challenger/mock_invoice_client.go @@ -0,0 +1,118 @@ +package challenger + +import ( + "context" + "sync" + "time" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntypes" + "google.golang.org/grpc" +) + +type invoiceStreamMock struct { + lnrpc.Lightning_SubscribeInvoicesClient + + updateChan chan *lnrpc.Invoice + errChan chan error + quit chan struct{} +} + +func (i *invoiceStreamMock) Recv() (*lnrpc.Invoice, error) { + select { + case msg := <-i.updateChan: + return msg, nil + + case err := <-i.errChan: + return nil, err + + case <-i.quit: + return nil, context.Canceled + } +} + +type MockInvoiceClient struct { + invoices []*lnrpc.Invoice + updateChan chan *lnrpc.Invoice + errChan chan error + quit chan struct{} + + lastAddIndex uint64 +} + +// ListInvoices returns a paginated list of all invoices known to lnd. +func (m *MockInvoiceClient) ListInvoices(_ context.Context, + _ *lnrpc.ListInvoiceRequest, + _ ...grpc.CallOption) (*lnrpc.ListInvoiceResponse, error) { + + return &lnrpc.ListInvoiceResponse{ + Invoices: m.invoices, + }, nil +} + +// SubscribeInvoices subscribes to updates on invoices. +func (m *MockInvoiceClient) SubscribeInvoices(_ context.Context, + in *lnrpc.InvoiceSubscription, _ ...grpc.CallOption) ( + lnrpc.Lightning_SubscribeInvoicesClient, error) { + + m.lastAddIndex = in.AddIndex + + return &invoiceStreamMock{ + updateChan: m.updateChan, + errChan: m.errChan, + quit: m.quit, + }, nil +} + +// AddInvoice adds a new invoice to lnd. +func (m *MockInvoiceClient) AddInvoice(_ context.Context, in *lnrpc.Invoice, + _ ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { + + m.invoices = append(m.invoices, in) + + return &lnrpc.AddInvoiceResponse{ + RHash: in.RHash, + PaymentRequest: in.PaymentRequest, + AddIndex: uint64(len(m.invoices) - 1), + }, nil +} + +func (m *MockInvoiceClient) stop() { + close(m.quit) +} + +func NewChallenger() (*LndChallenger, *MockInvoiceClient, chan error) { + mockClient := &MockInvoiceClient{ + updateChan: make(chan *lnrpc.Invoice), + errChan: make(chan error, 1), + quit: make(chan struct{}), + } + genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) { + return newInvoice(lntypes.ZeroHash, 99, lnrpc.Invoice_OPEN), + nil + } + invoicesMtx := &sync.Mutex{} + mainErrChan := make(chan error) + return &LndChallenger{ + client: mockClient, + genInvoiceReq: genInvoiceReq, + invoiceStates: make(map[lntypes.Hash]lnrpc.Invoice_InvoiceState), + quit: make(chan struct{}), + invoicesMtx: invoicesMtx, + invoicesCond: sync.NewCond(invoicesMtx), + errChan: mainErrChan, + }, mockClient, mainErrChan +} + +func newInvoice(hash lntypes.Hash, addIndex uint64, + state lnrpc.Invoice_InvoiceState) *lnrpc.Invoice { + + return &lnrpc.Invoice{ + PaymentRequest: "foo", + RHash: hash[:], + AddIndex: addIndex, + State: state, + CreationDate: time.Now().Unix(), + Expiry: 10, + } +} From 8152075038134e96aa0c38a2cea9c2baf0318770 Mon Sep 17 00:00:00 2001 From: Turtle Date: Sun, 5 Mar 2023 23:13:34 -0600 Subject: [PATCH 5/5] interceptor: move client interceptor to its own package --- {lsat => interceptor}/client_interceptor.go | 33 +++++++------- .../client_interceptor_test.go | 44 +++++++------------ interceptor/log.go | 26 +++++++++++ log.go | 4 ++ lsat/store.go | 11 +++-- lsat/store_test.go | 14 +++--- lsat/test_utils.go | 18 ++++++++ lsat/token.go | 28 ++++++------ proxy/proxy_test.go | 4 +- 9 files changed, 114 insertions(+), 68 deletions(-) rename {lsat => interceptor}/client_interceptor.go (95%) rename {lsat => interceptor}/client_interceptor_test.go (92%) create mode 100644 interceptor/log.go create mode 100644 lsat/test_utils.go diff --git a/lsat/client_interceptor.go b/interceptor/client_interceptor.go similarity index 95% rename from lsat/client_interceptor.go rename to interceptor/client_interceptor.go index b6a277b..e4ba1df 100644 --- a/lsat/client_interceptor.go +++ b/interceptor/client_interceptor.go @@ -1,4 +1,4 @@ -package lsat +package interceptor import ( "context" @@ -11,6 +11,7 @@ import ( "time" "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" @@ -77,7 +78,7 @@ var ( // connection to lnd to automatically pay for an authentication token. type ClientInterceptor struct { lnd *lndclient.LndServices - store Store + store lsat.Store callTimeout time.Duration maxCost btcutil.Amount maxFee btcutil.Amount @@ -85,10 +86,10 @@ type ClientInterceptor struct { allowInsecure bool } -// NewInterceptor creates a new gRPC client interceptor that uses the provided -// lnd connection to automatically acquire and pay for LSAT tokens, unless the -// indicated store already contains a usable token. -func NewInterceptor(lnd *lndclient.LndServices, store Store, +// NewClientInterceptor creates a new gRPC client interceptor that uses the +// provided lnd connection to automatically acquire and pay for LSAT tokens, +// unless the indicated store already contains a usable token. +func NewClientInterceptor(lnd *lndclient.LndServices, store lsat.Store, rpcCallTimeout time.Duration, maxCost, maxFee btcutil.Amount, allowInsecure bool) *ClientInterceptor { @@ -108,7 +109,7 @@ type interceptContext struct { mainCtx context.Context opts []grpc.CallOption metadata *metadata.MD - token *Token + token *lsat.Token } // UnaryInterceptor is an interceptor method that can be used directly by gRPC @@ -223,7 +224,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context, iCtx.token, err = i.store.CurrentToken() switch { // If there is no token yet, nothing to do at this point. - case err == ErrNoToken: + case err == lsat.ErrNoToken: // Some other error happened that we have to surface. case err != nil: @@ -235,7 +236,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context, // payment just yet, since we don't even know if a token is required for // this call. We also never send a pending payment to the server since // we know it's not valid. - case !iCtx.token.isPending(): + case !iCtx.token.IsPending(): if err = i.addLsatCredentials(iCtx); err != nil { log.Errorf("Adding macaroon to request failed: %v", err) return nil, fmt.Errorf("adding macaroon failed: %v", @@ -257,7 +258,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context, func (i *ClientInterceptor) handlePayment(iCtx *interceptContext) error { switch { // Resume/track a pending payment if it was interrupted for some reason. - case iCtx.token != nil && iCtx.token.isPending(): + case iCtx.token != nil && iCtx.token.IsPending(): log.Infof("Payment of LSAT token is required, resuming/" + "tracking previous payment from pending LSAT token") err := i.trackPayment(iCtx.mainCtx, iCtx.token) @@ -321,7 +322,7 @@ func (i *ClientInterceptor) addLsatCredentials(iCtx *interceptContext) error { return err } iCtx.opts = append(iCtx.opts, grpc.PerRPCCredentials( - NewMacaroonCredential(macaroon, i.allowInsecure), + lsat.NewMacaroonCredential(macaroon, i.allowInsecure), )) return nil } @@ -330,7 +331,7 @@ func (i *ClientInterceptor) addLsatCredentials(iCtx *interceptContext) error { // to pay the invoice encoded in them, returning a paid LSAT token if // successful. func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) ( - *Token, error) { + *lsat.Token, error) { // First parse the authentication header that was stored in the // metadata. @@ -367,7 +368,7 @@ func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) ( // Create and store the pending token so we can resume the payment in // case the payment is interrupted somehow. - token, err := tokenFromChallenge(macBytes, invoice.PaymentHash) + token, err := lsat.TokenFromChallenge(macBytes, invoice.PaymentHash) if err != nil { return nil, fmt.Errorf("unable to create token: %v", err) } @@ -407,7 +408,9 @@ func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) ( // trackPayment tries to resume a pending payment by tracking its state and // waiting for a conclusive result. -func (i *ClientInterceptor) trackPayment(ctx context.Context, token *Token) error { +func (i *ClientInterceptor) trackPayment(ctx context.Context, + token *lsat.Token) error { + // Lookup state of the payment. paymentStateCtx, cancel := context.WithCancel(ctx) defer cancel() @@ -486,7 +489,7 @@ func IsPaymentRequired(err error) bool { // extractPaymentDetails extracts the preimage and amounts paid for a payment // from the payment status and stores them in the token. -func extractPaymentDetails(token *Token, status lndclient.PaymentStatus) { +func extractPaymentDetails(token *lsat.Token, status lndclient.PaymentStatus) { token.Preimage = status.Preimage token.AmountPaid = status.Value token.RoutingFeePaid = status.Fee diff --git a/lsat/client_interceptor_test.go b/interceptor/client_interceptor_test.go similarity index 92% rename from lsat/client_interceptor_test.go rename to interceptor/client_interceptor_test.go index 624ed97..4e9f1a4 100644 --- a/lsat/client_interceptor_test.go +++ b/interceptor/client_interceptor_test.go @@ -1,4 +1,4 @@ -package lsat +package interceptor import ( "context" @@ -10,6 +10,7 @@ import ( "time" "github.com/lightninglabs/aperture/internal/test" + "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -36,21 +37,21 @@ type interceptTestCase struct { } type mockStore struct { - token *Token + token *lsat.Token } -func (s *mockStore) CurrentToken() (*Token, error) { +func (s *mockStore) CurrentToken() (*lsat.Token, error) { if s.token == nil { - return nil, ErrNoToken + return nil, lsat.ErrNoToken } return s.token, nil } -func (s *mockStore) AllTokens() (map[string]*Token, error) { - return map[string]*Token{"foo": s.token}, nil +func (s *mockStore) AllTokens() (map[string]*lsat.Token, error) { + return map[string]*lsat.Token{"foo": s.token}, nil } -func (s *mockStore) StoreToken(token *Token) error { +func (s *mockStore) StoreToken(token *lsat.Token) error { s.token = token return nil } @@ -64,11 +65,11 @@ var ( lnd = test.NewMockLnd() store = &mockStore{} testTimeout = 5 * time.Second - interceptor = NewInterceptor( + interceptor = NewClientInterceptor( &lnd.LndServices, store, testTimeout, DefaultMaxCostSats, DefaultMaxRoutingFeeSats, false, ) - testMac = makeMac() + testMac = lsat.MakeMac() testMacBytes = serializeMac(testMac) testMacHex = hex.EncodeToString(testMacBytes) paidPreimage = lntypes.Preimage{1, 2, 3, 4, 5} @@ -135,7 +136,7 @@ var ( expectMacaroonCall2: false, }, { name: "auth required, has pending token", - initialPreimage: &zeroPreimage, + initialPreimage: &lsat.ZeroPreimage, interceptor: interceptor, resetCb: func() { resetBackend( @@ -166,7 +167,7 @@ var ( expectMacaroonCall2: true, }, { name: "auth required, has pending but expired token", - initialPreimage: &zeroPreimage, + initialPreimage: &lsat.ZeroPreimage, interceptor: interceptor, resetCb: func() { resetBackend( @@ -207,7 +208,7 @@ var ( }, { name: "auth required, no token yet, cost limit", initialPreimage: nil, - interceptor: NewInterceptor( + interceptor: NewClientInterceptor( &lnd.LndServices, store, testTimeout, 100, DefaultMaxRoutingFeeSats, false, ), @@ -393,7 +394,7 @@ func testInterceptor(t *testing.T, tc interceptTestCase, require.NoError(t, err) require.Equal(t, paidPreimage, storeToken.Preimage) } else { - require.Equal(t, ErrNoToken, err) + require.Equal(t, lsat.ErrNoToken, err) } if tc.expectMacaroonCall2 { require.Len(t, callMD, 1) @@ -405,27 +406,16 @@ func testInterceptor(t *testing.T, tc interceptTestCase, require.Equal(t, tc.expectBackendCalls, numBackendCalls) } -func makeToken(preimage *lntypes.Preimage) *Token { +func makeToken(preimage *lntypes.Preimage) *lsat.Token { if preimage == nil { return nil } - return &Token{ + return &lsat.Token{ Preimage: *preimage, - baseMac: testMac, + BaseMac: testMac, } } -func makeMac() *macaroon.Macaroon { - dummyMac, err := macaroon.New( - []byte("aabbccddeeff00112233445566778899"), []byte("AA=="), - "LSAT", macaroon.LatestVersion, - ) - if err != nil { - panic(fmt.Errorf("unable to create macaroon: %v", err)) - } - return dummyMac -} - func serializeMac(mac *macaroon.Macaroon) []byte { macBytes, err := mac.MarshalBinary() if err != nil { diff --git a/interceptor/log.go b/interceptor/log.go new file mode 100644 index 0000000..7aa32f6 --- /dev/null +++ b/interceptor/log.go @@ -0,0 +1,26 @@ +package interceptor + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "INPR" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/log.go b/log.go index 6111bb6..1688d89 100644 --- a/log.go +++ b/log.go @@ -4,6 +4,7 @@ import ( "github.com/btcsuite/btclog" "github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/challenger" + "github.com/lightninglabs/aperture/interceptor" "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/proxy" "github.com/lightninglabs/lndclient" @@ -31,6 +32,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { lnd.AddSubLogger( root, challenger.Subsystem, intercept, challenger.UseLogger, ) + lnd.AddSubLogger( + root, interceptor.Subsystem, intercept, interceptor.UseLogger, + ) lnd.AddSubLogger(root, lsat.Subsystem, intercept, lsat.UseLogger) lnd.AddSubLogger(root, proxy.Subsystem, intercept, proxy.UseLogger) lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger) diff --git a/lsat/store.go b/lsat/store.go index 736e19f..b4eaafe 100644 --- a/lsat/store.go +++ b/lsat/store.go @@ -24,7 +24,12 @@ var ( // errNoReplace is the error that is returned if a new token is // being written to a store that already contains a paid token. errNoReplace = errors.New("won't replace existing paid token with " + - "new token. " + manualRetryHint) + "new token. " + ManualRetryHint) + + // ManualRetryHint is the error text we return to tell the user how a + // token payment can be retried if the payment fails. + ManualRetryHint = "consider removing pending token file if error " + + "persists. use 'listauth' command to find out token file name" ) // Store is an interface that allows users to store and retrieve an LSAT token. @@ -152,7 +157,7 @@ func (f *FileStore) StoreToken(newToken *Token) error { case err == ErrNoToken: // What's the target file name we are going to write? newFileName := f.fileName - if newToken.isPending() { + if newToken.IsPending() { newFileName = f.fileNamePending } return os.WriteFile(newFileName, bytes, 0600) @@ -162,7 +167,7 @@ func (f *FileStore) StoreToken(newToken *Token) error { return err // Replace a pending token with a paid one. - case currentToken.isPending() && !newToken.isPending(): + case currentToken.IsPending() && !newToken.IsPending(): // Make sure we replace the the same token, just with a // different state. if currentToken.PaymentHash != newToken.PaymentHash { diff --git a/lsat/store_test.go b/lsat/store_test.go index 0c80a3c..403b7fc 100644 --- a/lsat/store_test.go +++ b/lsat/store_test.go @@ -22,11 +22,11 @@ func TestFileStore(t *testing.T) { paidPreimage = lntypes.Preimage{1, 2, 3, 4, 5} paidToken = &Token{ Preimage: paidPreimage, - baseMac: makeMac(), + BaseMac: MakeMac(), } pendingToken = &Token{ - Preimage: zeroPreimage, - baseMac: makeMac(), + Preimage: ZeroPreimage, + BaseMac: MakeMac(), } ) @@ -61,7 +61,7 @@ func TestFileStore(t *testing.T) { if err != nil { t.Fatalf("could not read pending token: %v", err) } - if !token.baseMac.Equal(pendingToken.baseMac) { + if !token.BaseMac.Equal(pendingToken.BaseMac) { t.Fatalf("expected macaroon to match") } tokens, err = store.AllTokens() @@ -73,7 +73,7 @@ func TestFileStore(t *testing.T) { len(tokens), 1) } for key := range tokens { - if !tokens[key].baseMac.Equal(pendingToken.baseMac) { + if !tokens[key].BaseMac.Equal(pendingToken.BaseMac) { t.Fatalf("expected macaroon to match") } } @@ -96,7 +96,7 @@ func TestFileStore(t *testing.T) { if err != nil { t.Fatalf("could not read pending token: %v", err) } - if !token.baseMac.Equal(paidToken.baseMac) { + if !token.BaseMac.Equal(paidToken.BaseMac) { t.Fatalf("expected macaroon to match") } tokens, err = store.AllTokens() @@ -108,7 +108,7 @@ func TestFileStore(t *testing.T) { len(tokens), 1) } for key := range tokens { - if !tokens[key].baseMac.Equal(paidToken.baseMac) { + if !tokens[key].BaseMac.Equal(paidToken.BaseMac) { t.Fatalf("expected macaroon to match") } } diff --git a/lsat/test_utils.go b/lsat/test_utils.go new file mode 100644 index 0000000..cba947d --- /dev/null +++ b/lsat/test_utils.go @@ -0,0 +1,18 @@ +package lsat + +import ( + "fmt" + + "gopkg.in/macaroon.v2" +) + +func MakeMac() *macaroon.Macaroon { + dummyMac, err := macaroon.New( + []byte("aabbccddeeff00112233445566778899"), []byte("AA=="), + "LSAT", macaroon.LatestVersion, + ) + if err != nil { + panic(fmt.Errorf("unable to create macaroon: %v", err)) + } + return dummyMac +} diff --git a/lsat/token.go b/lsat/token.go index 1be010e..40f8d83 100644 --- a/lsat/token.go +++ b/lsat/token.go @@ -12,9 +12,9 @@ import ( ) var ( - // zeroPreimage is an empty, invalid payment preimage that is used to + // ZeroPreimage is an empty, invalid payment preimage that is used to // initialize pending tokens with. - zeroPreimage lntypes.Preimage + ZeroPreimage lntypes.Preimage ) // Token is the main type to store an LSAT token in. @@ -40,15 +40,15 @@ type Token struct { // TimeCreated is the moment when this token was created. TimeCreated time.Time - // baseMac is the base macaroon in its original form as baked by the + // BaseMac is the base macaroon in its original form as baked by the // authentication server. No client side caveats have been added to it // yet. - baseMac *macaroon.Macaroon + BaseMac *macaroon.Macaroon } -// tokenFromChallenge parses the parts that are present in the challenge part +// TokenFromChallenge parses the parts that are present in the challenge part // of the LSAT auth protocol which is the macaroon and the payment hash. -func tokenFromChallenge(baseMac []byte, paymentHash *[32]byte) (*Token, error) { +func TokenFromChallenge(baseMac []byte, paymentHash *[32]byte) (*Token, error) { // First, validate that the macaroon is valid and can be unmarshaled. mac := &macaroon.Macaroon{} err := mac.UnmarshalBinary(baseMac) @@ -58,8 +58,8 @@ func tokenFromChallenge(baseMac []byte, paymentHash *[32]byte) (*Token, error) { token := &Token{ TimeCreated: time.Now(), - baseMac: mac, - Preimage: zeroPreimage, + BaseMac: mac, + Preimage: ZeroPreimage, } hash, err := lntypes.MakeHash(paymentHash[:]) if err != nil { @@ -72,7 +72,7 @@ func tokenFromChallenge(baseMac []byte, paymentHash *[32]byte) (*Token, error) { // BaseMacaroon returns the base macaroon as received from the authentication // server. func (t *Token) BaseMacaroon() *macaroon.Macaroon { - return t.baseMac.Clone() + return t.BaseMac.Clone() } // PaidMacaroon returns the base macaroon with the proof of payment (preimage) @@ -96,17 +96,17 @@ func (t *Token) IsValid() bool { return true } -// isPending returns true if the payment for the LSAT is still in flight and we +// IsPending returns true if the payment for the LSAT is still in flight and we // haven't received the preimage yet. -func (t *Token) isPending() bool { - return t.Preimage == zeroPreimage +func (t *Token) IsPending() bool { + return t.Preimage == ZeroPreimage } // serializeToken returns a byte-serialized representation of the token. func serializeToken(t *Token) ([]byte, error) { var b bytes.Buffer - baseMacBytes, err := t.baseMac.MarshalBinary() + baseMacBytes, err := t.BaseMac.MarshalBinary() if err != nil { return nil, err } @@ -163,7 +163,7 @@ func deserializeToken(value []byte) (*Token, error) { return nil, err } - token, err := tokenFromChallenge(macBytes, &paymentHash) + token, err := TokenFromChallenge(macBytes, &paymentHash) if err != nil { return nil, err } diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 1e0e439..3225a5d 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -15,7 +15,7 @@ import ( "time" "github.com/lightninglabs/aperture/auth" - "github.com/lightninglabs/aperture/lsat" + "github.com/lightninglabs/aperture/interceptor" "github.com/lightninglabs/aperture/proxy" proxytest "github.com/lightninglabs/aperture/proxy/testdata" "github.com/lightningnetwork/lnd/cert" @@ -309,7 +309,7 @@ func runGRPCTest(t *testing.T, tc *testCase) { grpc.Trailer(&captureMetadata), ) require.Error(t, err) - require.True(t, lsat.IsPaymentRequired(err)) + require.True(t, interceptor.IsPaymentRequired(err)) // We expect the WWW-Authenticate header field to be set to an LSAT // auth response.