mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-19 01:54:20 +01:00
kirin: allow handling client requests over Tor onion services
In this commit, we integrate Tor onion services into the proxy. Clients can now make their requests through Tor's encrypted network. To make this possible, there were a few quirks, the most important being that clients were unable to establish encrypted HTTP/2 connections due to TLS certificates not being able to verify onion services. To work around this, we now spin up an additional HTTP/2 server _without TLS_ that's not exposed to the outside world and can only be accessed through the onion services, which already provide encryption. Once the onion services are created, we store their private keys within etcd to ensure we can recover them later on as the proxy is intended to be long-lived.
This commit is contained in:
10
config.go
10
config.go
@@ -33,6 +33,14 @@ type authConfig struct {
|
|||||||
Network string `long:"network"`
|
Network string `long:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type torConfig struct {
|
||||||
|
Control string `long:"control" description:"The host:port of the Tor instance."`
|
||||||
|
ListenPort uint16 `long:"listenport" description:"The port we should listen on for client requests over Tor. Note that this port should not be exposed to the outside world, it is only intended to be reached by clients through the onion service."`
|
||||||
|
VirtualPort uint16 `long:"virtualport" description:"The port through which the onion services created can be reached at."`
|
||||||
|
V2 bool `long:"v2" description:"Whether we should listen for client requests through a v2 onion service."`
|
||||||
|
V3 bool `long:"v3" description:"Whether we should listen for client requests through a v3 onion service."`
|
||||||
|
}
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
// ListenAddr is the listening address that we should use to allow Kirin
|
// ListenAddr is the listening address that we should use to allow Kirin
|
||||||
// to listen for requests.
|
// to listen for requests.
|
||||||
@@ -54,6 +62,8 @@ type config struct {
|
|||||||
|
|
||||||
Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."`
|
Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."`
|
||||||
|
|
||||||
|
Tor *torConfig `long:"tor" description:"Configuration for the Tor instance backing the proxy."`
|
||||||
|
|
||||||
// Services is a list of JSON objects in string format, which specify
|
// Services is a list of JSON objects in string format, which specify
|
||||||
// each backend service to Kirin.
|
// each backend service to Kirin.
|
||||||
Services []*proxy.Service `long:"service" description:"Configurations for each Kirin backend service."`
|
Services []*proxy.Service `long:"service" description:"Configurations for each Kirin backend service."`
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.8 // indirect
|
github.com/json-iterator/go v1.1.8 // indirect
|
||||||
github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a
|
github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a
|
||||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194
|
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498
|
||||||
github.com/lightningnetwork/lnd/cert v1.0.0
|
github.com/lightningnetwork/lnd/cert v1.0.0
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/soheilhy/cmux v0.1.4 // indirect
|
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||||
@@ -26,7 +26,7 @@ require (
|
|||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||||
go.uber.org/zap v1.13.0 // indirect
|
go.uber.org/zap v1.13.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
|
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
|
||||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 // indirect
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343
|
||||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
|
||||||
google.golang.org/grpc v1.25.1
|
google.golang.org/grpc v1.25.1
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -29,8 +29,9 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9
|
|||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
github.com/btcsuite/btcwallet v0.11.0 h1:XhwqdhEchy5a0q6R+y3F82roD2hYycPCHovgNyJS08w=
|
|
||||||
github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s=
|
github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s=
|
||||||
|
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623 h1:ZuJRjucNsTmlrbZncsqzD0z3EaXrOobCx2I4lc12R4g=
|
||||||
|
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623/go.mod h1:1O1uRHMPXHdwA4/od8nqYqrgclVKp+wtfXUAqHmeRvE=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
||||||
@@ -39,8 +40,9 @@ github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf
|
|||||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
|
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw=
|
github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw=
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.1.0 h1:JHAL7wZ8pX4SULabeAv/wPO9sseRWMGzE80lfVmRw6Y=
|
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
||||||
|
github.com/btcsuite/btcwallet/walletdb v1.2.0 h1:E0+M4jHOToAvGWZ27ew5AaDAHDi6fUiXkjUJUnoEOD0=
|
||||||
|
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
|
||||||
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
|
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
|
||||||
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
|
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
|
||||||
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
|
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
|
||||||
@@ -173,10 +175,12 @@ github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a/go.mod
|
|||||||
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
|
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
|
||||||
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
|
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
|
||||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
||||||
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d h1:U50MHOOeL6gR3Ee/l0eMvZMpmRo+ydzmlQuIruCyCsA=
|
|
||||||
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194 h1:PCzjJcVWcMbkiQvzFNc3ta0JmiMprFDqzMZsSpd/km8=
|
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
|
||||||
|
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||||
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4=
|
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4=
|
||||||
|
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498 h1:v9WzPrzY/BOPfs7D1Lg5IuIxhao3BAJaL8EFFcLTzTY=
|
||||||
|
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498/go.mod h1:fImtTwhIXK91glN8iArkrGuScc0sNEKpZq43pTvVq3w=
|
||||||
github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw=
|
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/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
|
||||||
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
|
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
|
||||||
@@ -297,6 +301,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
85
kirin.go
85
kirin.go
@@ -17,7 +17,10 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
"github.com/lightningnetwork/lnd/cert"
|
"github.com/lightningnetwork/lnd/cert"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,9 +80,10 @@ func start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
server := &http.Server{
|
handler := http.HandlerFunc(servicesProxy.ServeHTTP)
|
||||||
|
httpsServer := &http.Server{
|
||||||
Addr: cfg.ListenAddr,
|
Addr: cfg.ListenAddr,
|
||||||
Handler: http.HandlerFunc(servicesProxy.ServeHTTP),
|
Handler: handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create TLS certificates.
|
// Create TLS certificates.
|
||||||
@@ -112,7 +116,7 @@ func start() error {
|
|||||||
log.Errorf("autocert http: %v", err)
|
log.Errorf("autocert http: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
server.TLSConfig = &tls.Config{
|
httpsServer.TLSConfig = &tls.Config{
|
||||||
GetCertificate: manager.GetCertificate,
|
GetCertificate: manager.GetCertificate,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +143,43 @@ func start() error {
|
|||||||
// The ListenAndServeTLS below will block until shut down or an 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
|
// occurs. So we can just defer a cleanup function here that will close
|
||||||
// everything on shutdown.
|
// everything on shutdown.
|
||||||
defer cleanup(etcdClient, server)
|
defer cleanup(etcdClient, httpsServer)
|
||||||
|
|
||||||
// Finally start the server.
|
// Finally start the server.
|
||||||
log.Infof("Starting the server, listening on %s.", cfg.ListenAddr)
|
log.Infof("Starting the server, listening on %s.", cfg.ListenAddr)
|
||||||
return server.ListenAndServeTLS(tlsCertFile, tlsKeyFile)
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- httpsServer.ListenAndServeTLS(tlsCertFile, tlsKeyFile)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If we need to listen over Tor as well, we'll set up the onion
|
||||||
|
// services now. We're not able to use TLS for onion services since they
|
||||||
|
// can't be verified, so we'll spin up an additional HTTP/2 server
|
||||||
|
// _without_ TLS that is not exposed to the outside world. This server
|
||||||
|
// will only be reached through the onion services, which already
|
||||||
|
// provide encryption, so running this additional HTTP server should be
|
||||||
|
// relatively safe.
|
||||||
|
if cfg.Tor.V2 || cfg.Tor.V3 {
|
||||||
|
torController, err := initTorListener(cfg, etcdClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = torController.Stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("localhost:%d", cfg.Tor.ListenPort),
|
||||||
|
Handler: h2c.NewHandler(handler, &http2.Server{}),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
errChan <- httpServer.ListenAndServe()
|
||||||
|
}()
|
||||||
|
defer httpServer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileExists reports whether the named file or directory exists.
|
// fileExists reports whether the named file or directory exists.
|
||||||
@@ -195,6 +231,45 @@ func setupLogging(cfg *config) error {
|
|||||||
return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter)
|
return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initTorListener initiates a Tor controller instance with the Tor server
|
||||||
|
// specified in the config. Onion services will be created over which the proxy
|
||||||
|
// can be reached at.
|
||||||
|
func initTorListener(cfg *config, etcd *clientv3.Client) (*tor.Controller, error) {
|
||||||
|
// Establish a controller connection with the backing Tor server and
|
||||||
|
// proceed to create the requested onion services.
|
||||||
|
onionCfg := tor.AddOnionConfig{
|
||||||
|
VirtualPort: int(cfg.Tor.VirtualPort),
|
||||||
|
TargetPorts: []int{int(cfg.Tor.ListenPort)},
|
||||||
|
Store: newOnionStore(etcd),
|
||||||
|
}
|
||||||
|
torController := tor.NewController(cfg.Tor.Control, "", "")
|
||||||
|
if err := torController.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Tor.V2 {
|
||||||
|
onionCfg.Type = tor.V2
|
||||||
|
addr, err := torController.AddOnion(onionCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Listening over Tor on %v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Tor.V3 {
|
||||||
|
onionCfg.Type = tor.V3
|
||||||
|
addr, err := torController.AddOnion(onionCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Listening over Tor on %v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return torController, nil
|
||||||
|
}
|
||||||
|
|
||||||
// createProxy creates the proxy with all the services it needs.
|
// createProxy creates the proxy with all the services it needs.
|
||||||
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator,
|
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator,
|
||||||
etcdClient *clientv3.Client) (*proxy.Proxy, error) {
|
etcdClient *clientv3.Client) (*proxy.Proxy, error) {
|
||||||
|
|||||||
98
onion_store.go
Normal file
98
onion_store.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package kirin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// onionDir is the directory we'll use to store all onion service
|
||||||
|
// related information.
|
||||||
|
onionDir = "onion"
|
||||||
|
|
||||||
|
// onionV2Dir is the directory we'll use to store a v2 onion service's
|
||||||
|
// private key, such that it can be restored after restarts.
|
||||||
|
onionV2Dir = "v2"
|
||||||
|
|
||||||
|
// onionV2Dir is the directory we'll use to store a v3 onion service's
|
||||||
|
// private key, such that it can be restored after restarts.
|
||||||
|
onionV3Dir = "v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// onionPath returns the full path to an onion service's private key of the
|
||||||
|
// given type.
|
||||||
|
func onionPath(onionType tor.OnionType) (string, error) {
|
||||||
|
var typeDir string
|
||||||
|
switch onionType {
|
||||||
|
case tor.V2:
|
||||||
|
typeDir = onionV2Dir
|
||||||
|
case tor.V3:
|
||||||
|
typeDir = onionV3Dir
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown onion type %v", onionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(
|
||||||
|
[]string{topLevelKey, onionDir, typeDir}, etcdKeyDelimeter,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// onionStore is an etcd-based implementation of tor.OnionStore.
|
||||||
|
type onionStore struct {
|
||||||
|
*clientv3.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compile-time constraint to ensure onionStore implements tor.OnionStore.
|
||||||
|
var _ tor.OnionStore = (*onionStore)(nil)
|
||||||
|
|
||||||
|
// newOnionStore creates an etcd-based implementation of tor.OnionStore.
|
||||||
|
func newOnionStore(client *clientv3.Client) *onionStore {
|
||||||
|
return &onionStore{Client: client}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorePrivateKey stores the given private key.
|
||||||
|
func (s *onionStore) StorePrivateKey(onionType tor.OnionType,
|
||||||
|
privateKey []byte) error {
|
||||||
|
|
||||||
|
onionPath, err := onionPath(onionType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.Client.Put(context.Background(), onionPath, string(privateKey))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateKey retrieves a stored private key. If it is not found, then
|
||||||
|
// ErrNoPrivateKey should be returned.
|
||||||
|
func (s *onionStore) PrivateKey(onionType tor.OnionType) ([]byte, error) {
|
||||||
|
onionPath, err := onionPath(onionType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.Get(context.Background(), onionPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resp.Kvs) == 0 {
|
||||||
|
return nil, tor.ErrNoPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Kvs[0].Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePrivateKey securely removes the private key from the store.
|
||||||
|
func (s *onionStore) DeletePrivateKey(onionType tor.OnionType) error {
|
||||||
|
onionPath, err := onionPath(onionType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.Client.Delete(context.Background(), onionPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
81
onion_store_test.go
Normal file
81
onion_store_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package kirin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assertPrivateKeyExists is a helper to determine if the private key for an
|
||||||
|
// onion service exists in the store. If it does, it's compared against what's
|
||||||
|
// expected.
|
||||||
|
func assertPrivateKeyExists(t *testing.T, store *onionStore,
|
||||||
|
onionType tor.OnionType, expPrivateKey *[]byte) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
exists := expPrivateKey != nil
|
||||||
|
privateKey, err := store.PrivateKey(onionType)
|
||||||
|
switch {
|
||||||
|
case exists && err != nil:
|
||||||
|
t.Fatalf("unable to retrieve private key: %v", err)
|
||||||
|
case !exists && err != tor.ErrNoPrivateKey:
|
||||||
|
t.Fatalf("expected error ErrNoPrivateKey, got \"%v\"", err)
|
||||||
|
case exists:
|
||||||
|
if !bytes.Equal(privateKey, *expPrivateKey) {
|
||||||
|
t.Fatalf("expected private key %v, got %v",
|
||||||
|
string(*expPrivateKey), string(privateKey))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOnionStore ensures the different operations of the onionStore behave as
|
||||||
|
// espected.
|
||||||
|
func TestOnionStore(t *testing.T) {
|
||||||
|
etcdClient, serverCleanup := etcdSetup(t)
|
||||||
|
defer etcdClient.Close()
|
||||||
|
defer serverCleanup()
|
||||||
|
|
||||||
|
// Upon a fresh initialization of the store, no private keys should
|
||||||
|
// exist for any onion service type.
|
||||||
|
store := newOnionStore(etcdClient)
|
||||||
|
assertPrivateKeyExists(t, store, tor.V2, nil)
|
||||||
|
assertPrivateKeyExists(t, store, tor.V3, nil)
|
||||||
|
|
||||||
|
// Store a private key for a V2 onion service and check it was stored
|
||||||
|
// correctly.
|
||||||
|
privateKeyV2 := []byte("hide_me_plz_v2")
|
||||||
|
if err := store.StorePrivateKey(tor.V2, privateKeyV2); err != nil {
|
||||||
|
t.Fatalf("unable to store private key for v2 onion service: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
assertPrivateKeyExists(t, store, tor.V2, &privateKeyV2)
|
||||||
|
|
||||||
|
// Store a private key for a V3 onion service and check it was stored
|
||||||
|
// correctly.
|
||||||
|
privateKeyV3 := []byte("hide_me_plz_v3")
|
||||||
|
if err := store.StorePrivateKey(tor.V3, privateKeyV3); err != nil {
|
||||||
|
t.Fatalf("unable to store private key for v3 onion service: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
assertPrivateKeyExists(t, store, tor.V3, &privateKeyV3)
|
||||||
|
|
||||||
|
// Delete the private key for the V2 onion service and check that it was
|
||||||
|
// indeed successful.
|
||||||
|
if err := store.DeletePrivateKey(tor.V2); err != nil {
|
||||||
|
t.Fatalf("unable to remove private key for v2 onion service: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
assertPrivateKeyExists(t, store, tor.V2, nil)
|
||||||
|
|
||||||
|
// Delete the private key for the V3 onion service and check that it was
|
||||||
|
// indeed successful.
|
||||||
|
if err := store.DeletePrivateKey(tor.V3); err != nil {
|
||||||
|
t.Fatalf("unable to remove private key for v3 onion service: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
assertPrivateKeyExists(t, store, tor.V3, nil)
|
||||||
|
}
|
||||||
@@ -10,6 +10,13 @@ authenticator:
|
|||||||
macdir: "/path/to/lnd/data/chain/bitcoin/simnet"
|
macdir: "/path/to/lnd/data/chain/bitcoin/simnet"
|
||||||
network: "simnet"
|
network: "simnet"
|
||||||
|
|
||||||
|
tor:
|
||||||
|
control: "localhost:9051"
|
||||||
|
listenport: 8082
|
||||||
|
virtualport: 8082
|
||||||
|
v2: false
|
||||||
|
v3: false
|
||||||
|
|
||||||
etcd:
|
etcd:
|
||||||
host: "localhost:2379"
|
host: "localhost:2379"
|
||||||
user: "user"
|
user: "user"
|
||||||
|
|||||||
Reference in New Issue
Block a user