Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions docs/release-notes/release-notes-0.7.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
- [An integration test flake was
fixed](https://github.com/lightninglabs/taproot-assets/pull/1651).

- Fixed two send related bugs that would lead to either a `invalid transfer
asset witness` or `unable to fund address send: error funding packet: unable
- Fixed two send related bugs that would lead to either a `invalid transfer
asset witness` or `unable to fund address send: error funding packet: unable
to list eligible coins: unable to query commitments: mismatch of managed utxo
and constructed tap commitment root` error when sending assets.
The [PR that fixed the two
Expand Down Expand Up @@ -101,6 +101,18 @@
controls whether the peer's identity public key is sent to the local price
oracle when querying asset price rates.

- [TLS connections with price oracles will now be constructed by
default](https://github.com/lightninglabs/taproot-assets/pull/1775), using
the operating system's root CA list for certificate verification.
`experimental.rfq.priceoracletls` (default `true`) can be set to `false`
to disable TLS entirely. For certificate-level configuration,
`experimental.rfq.priceoracletlsnosystemcas` (default `false`) can be set
to `true` to disable use of the OS's root CA list, and
`experimental.rfq.priceoracletlscertpath` allows a custom (root CA or
self-signed) certificate to be used.
`experimental.rfq.priceoracletlsinsecure` can be used to skip certificate
verification (default `false`).

## RPC Additions

- The [price oracle RPC calls now have an intent, optional peer ID and metadata
Expand All @@ -123,14 +135,14 @@
- [Rename](https://github.com/lightninglabs/taproot-assets/pull/1682) the
`MintAsset` RPC message field from `universe_commitments` to
`enable_supply_commitments`.
- The `SubscribeSendEvents` RPC now supports [historical event replay of
- The `SubscribeSendEvents` RPC now supports [historical event replay of
completed sends with efficient database-level
filtering](https://github.com/lightninglabs/taproot-assets/pull/1685).
- [Add universe RPC endpoint FetchSupplyLeaves](https://github.com/lightninglabs/taproot-assets/pull/1693)
that allows users to fetch the supply leaves of a universe supply commitment.
This is useful for verification.

- A [new field `unconfirmed_transfers` was added to the response of the
- A [new field `unconfirmed_transfers` was added to the response of the
`ListBalances` RPC
method](https://github.com/lightninglabs/taproot-assets/pull/1691) to indicate
that unconfirmed asset-related transactions don't count toward the balance.
Expand Down
1 change: 1 addition & 0 deletions itest/tapd_harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ func newTapdHarness(t *testing.T, ht *harnessTest, cfg tapdConfig,
case len(opts.oracleServerAddress) > 0:
tapCfg.Experimental.Rfq.PriceOracleAddress =
opts.oracleServerAddress
tapCfg.Experimental.Rfq.PriceOracleTLSInsecure = true

default:
// Set the experimental config for the RFQ service.
Expand Down
8 changes: 8 additions & 0 deletions rfq/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const (
type CliConfig struct {
PriceOracleAddress string `long:"priceoracleaddress" description:"Price oracle gRPC server address (rfqrpc://<hostname>:<port>). To use the integrated mock, use the following value: use_mock_price_oracle_service_promise_to_not_use_on_mainnet"`

PriceOracleTLSDisable bool `long:"priceoracletlsdisable" description:"Disable TLS for price oracle communication."`

PriceOracleTLSInsecure bool `long:"priceoracletlsinsecure" description:"Disable price oracle certificate verification."`

PriceOracleTLSNoSystemCAs bool `long:"priceoracletlsnosystemcas" description:"Disable use of the operating system's list of root CA's when verifiying price oracle certificates."`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: s/verifiying/verifying


PriceOracleTLSCertPath string `long:"priceoracletlscertpath" description:"Path to a PEM-encoded x509 certificate to use when constructing a TLS connection with a price oracle."`

SendPriceHint bool `long:"sendpricehint" description:"Send a price hint from the local price oracle to the RFQ peer when requesting a quote. For privacy reasons, this should only be turned on for self-hosted or trusted price oracles."`

PriceOracleSendPeerId bool `long:"priceoraclesendpeerid" description:"Send the peer ID (public key of the peer) to the price oracle when requesting a price rate. For privacy reasons, this should only be turned on for self-hosted or trusted price oracles."`
Expand Down
46 changes: 7 additions & 39 deletions rfq/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package rfq

import (
"context"
"crypto/tls"
"fmt"
"math"
"net/url"
Expand All @@ -16,8 +15,6 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

// PriceQueryIntent is an enum that represents the intent of a price rate
Expand Down Expand Up @@ -186,60 +183,31 @@ type RpcPriceOracle struct {
rawConn *grpc.ClientConn
}

// serverDialOpts returns the set of server options needed to connect to the
// price oracle RPC server using a TLS connection.
func serverDialOpts() ([]grpc.DialOption, error) {
var opts []grpc.DialOption

// Skip TLS certificate verification.
tlsConfig := tls.Config{InsecureSkipVerify: true}
transportCredentials := credentials.NewTLS(&tlsConfig)
opts = append(opts, grpc.WithTransportCredentials(transportCredentials))

return opts, nil
}

// insecureServerDialOpts returns the set of server options needed to connect to
// the price oracle RPC server using a TLS connection.
func insecureServerDialOpts() ([]grpc.DialOption, error) {
var opts []grpc.DialOption

// Skip TLS certificate verification.
opts = append(opts, grpc.WithTransportCredentials(
insecure.NewCredentials(),
))

return opts, nil
}

// NewRpcPriceOracle creates a new RPC price oracle handle given the address
// of the price oracle RPC server.
func NewRpcPriceOracle(addrStr string, dialInsecure bool) (*RpcPriceOracle,
func NewRpcPriceOracle(addrStr string, tlsConfig *TLSConfig) (*RpcPriceOracle,
error) {

addr, err := ParsePriceOracleAddress(addrStr)
if err != nil {
return nil, err
}

// Connect to the RPC server.
dialOpts, err := serverDialOpts()
// Create transport credentials and dial options from the supplied TLS
// config.
transportCredentials, err := configureTransportCredentials(tlsConfig)
if err != nil {
return nil, err
}

// Allow connecting to a non-TLS (h2c, http over cleartext) gRPC server,
// should be used for testing only.
if dialInsecure {
dialOpts, err = insecureServerDialOpts()
if err != nil {
return nil, err
}
dialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(transportCredentials),
}

// Formulate the server address dial string.
serverAddr := fmt.Sprintf("%s:%s", addr.Hostname(), addr.Port())

// Connect to the RPC server.
conn, err := grpc.Dial(serverAddr, dialOpts...)
if err != nil {
return nil, err
Expand Down
12 changes: 10 additions & 2 deletions rfq/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ func runQuerySalePriceTest(t *testing.T, tc *testCaseQuerySalePrice) {

// Create a new RPC price oracle client and connect to the mock service.
serviceAddr := fmt.Sprintf("rfqrpc://%s", testServiceAddress)
client, err := NewRpcPriceOracle(serviceAddr, true)
insecureTLS := &TLSConfig{Enabled: false}
client, err := NewRpcPriceOracle(serviceAddr, insecureTLS)
require.NoError(t, err)

// Query for an ask price.
Expand Down Expand Up @@ -239,6 +240,13 @@ type testCaseQueryPurchasePrice struct {
assetGroupKey *btcec.PublicKey
}

// insecureTLS returns a TLSConfig with TLS disabled.
func insecureTLS() *TLSConfig {
return &TLSConfig{
Enabled: false,
}
}

// runQueryPurchasePriceTest runs the RPC price oracle client QueryBuyPrice
// test.
func runQueryPurchasePriceTest(t *testing.T, tc *testCaseQueryPurchasePrice) {
Expand All @@ -251,7 +259,7 @@ func runQueryPurchasePriceTest(t *testing.T, tc *testCaseQueryPurchasePrice) {

// Create a new RPC price oracle client and connect to the mock service.
serviceAddr := fmt.Sprintf("rfqrpc://%s", testServiceAddress)
client, err := NewRpcPriceOracle(serviceAddr, true)
client, err := NewRpcPriceOracle(serviceAddr, insecureTLS())
require.NoError(t, err)

// Query for an ask price.
Expand Down
68 changes: 68 additions & 0 deletions rfq/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package rfq

import (
"crypto/tls"
"crypto/x509"

"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

// TLSConfig represents TLS configuration options for oracle connections.
type TLSConfig struct {
// Enabled indicates that we should use TLS.
Enabled bool

// InsecureSkipVerify disables certificate verification.
InsecureSkipVerify bool

// TrustSystemRootCAs indicates whether or not to use the operating
// system's root certificate authority list.
TrustSystemRootCAs bool

// CustomCertificates contains PEM data for additional root CA and
// self-signed certificates to trust.
CustomCertificates []byte
}

// configureTransportCredentials configures the TLS transport credentials to
// be used for RPC connections.
func configureTransportCredentials(
config *TLSConfig) (credentials.TransportCredentials, error) {

// If TLS is disabled, return insecure credentials.
if !config.Enabled {
return insecure.NewCredentials(), nil
}

// If we're to skip certificate verification, then return TLS
// credentials with certificate verification disabled.
if config.InsecureSkipVerify {
creds := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})
return creds, nil
}

// Initialize the certificate pool.
certPool, err := constructCertPool(config.TrustSystemRootCAs)
if err != nil {
return nil, err
}

// If we have any custom certificates, add them to the certificate
// pool.
certPool.AppendCertsFromPEM(config.CustomCertificates)

// Return the constructed transport credentials.
return credentials.NewClientTLSFromCert(certPool, ""), nil
}

// constructCertPool is a helper for constructing an initial certificate pool,
// depending on whether or not we should trust the system root CA list.
func constructCertPool(trustSystem bool) (*x509.CertPool, error) {
if trustSystem {
return x509.SystemCertPool()
}
return x509.NewCertPool(), nil
}
130 changes: 130 additions & 0 deletions rfq/tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package rfq

import (
"testing"

"github.com/stretchr/testify/require"
)

// validCertificate is a valid certificate.
const validCertificate = `-----BEGIN CERTIFICATE-----
MIICmjCCAYICCQCuu1gzY+BBKjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0
ZXN0MB4XDTI1MDgyODEwNDA1NVoXDTI1MDgyOTEwNDA1NVowDzENMAsGA1UEAwwE
dGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTWCm8l3d9nE2QK
TK8HJ36ftO8pK3//nb8Nj/p97FrPFSgzdgL1ZNJs4gP5/ZsU+iE6VeKhalHoSf6/
IMLe3ATTL0rWA1M6z7cw6ll8VS8NQMaMSFWNomncsxyoJAQde++SC5f1RwQJBD/0
gGB4bJIIqUHtT12m23GLX48d6JGEEi5kEQtk91S/QGnHtglzZ8CQOogDBzDhSHu2
jj4mKYDgkXcyAqN7DoDzoEcrpeAaeAwem8k1sFBeTtrqT1ot7Ey5KG+RUyJbdKGt
5adJiwH782NgsSnISQ2X7Sct6Uu0JzHKx9JzyABsA05tf3cNJkLhh1Is9edYI2e9
m0dqedECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAQOCs/7xZVPjabbhdv30mUJMG
lddi2A+R/5IRXW1MKnpemwiv4ZWYQ9PMTmuR7kqaF7AGLkvx+5sp2evUJN4x7vHP
ao6wihbdh+vBkrobE+Y9dE7nbkvMQSNi1sXzDnfZB9LqY9Huun2soUwBQNCMPVMa
Wo7g6udwyA48doEVJMjThFLPcW7xmsy6Ldew682m1kD8/ag+9qihX1IJyiqiEjha
3uT4CT+zEg0RJorEJKbR38fE4Uhx1wZO4zvjEg6qZeW/I4lw+UzSY5xV7lJ1EQvf
BcoNuBHB65RxQM5fpA7hkEFm1bxBoowGX2hx6VCCeBBwREISRfgvkUxZahUXNg==
-----END CERTIFICATE-----`

// invalidCertificate is an invalid certificate.
const invalidCertificate = `-----BEGIN CERTIFICATE-----
This is not a valid certificate
-----END CERTIFICATE-----`

// testCaseConfigureTransportCredentials is a test case for the
// configureTransportCredentials function.
type testCaseConfigureTransportCredentials struct {
name string

expectInsecure bool

tlsConfig *TLSConfig
}

// runConfigureTransportCredentialsTest tests that we get the expected
// security protocol from the provided test case.
func runConfigureTransportCredentialsTest(t *testing.T,
tc *testCaseConfigureTransportCredentials) {

creds, err := configureTransportCredentials(tc.tlsConfig)

// We should never see an error here.
require.Nil(t, err)

protocol := creds.Info().SecurityProtocol

if tc.expectInsecure {
require.Equal(t, "insecure", protocol)
return
}

require.Equal(t, "tls", protocol)
}

// defaultTLSConfig is the default TLS config.
func DefaultTLSConfig() *TLSConfig {
return &TLSConfig{
Enabled: true,
InsecureSkipVerify: false,
TrustSystemRootCAs: true,
}
}

// TestConfigureTransportCredentials tests the configureTransportCredentials
// function.
func TestConfigureTransportCredentials(t *testing.T) {
testCases := []*testCaseConfigureTransportCredentials{
{
name: "default configuration",
expectInsecure: false,
tlsConfig: DefaultTLSConfig(),
},
{
name: "tls disabled",
expectInsecure: true,
tlsConfig: &TLSConfig{
Enabled: false,
},
},
{
name: "trust os root CAs",
expectInsecure: false,
tlsConfig: &TLSConfig{
Enabled: true,
InsecureSkipVerify: false,
TrustSystemRootCAs: true,
},
},
{
name: "no trust os root CAs",
expectInsecure: false,
tlsConfig: &TLSConfig{
Enabled: true,
InsecureSkipVerify: false,
TrustSystemRootCAs: false,
},
},
{
name: "valid custom certificate",
expectInsecure: false,
tlsConfig: &TLSConfig{
Enabled: true,
InsecureSkipVerify: false,
TrustSystemRootCAs: false,
CustomCertificates: []byte(validCertificate),
},
},
{
name: "invalid custom certificate",
expectInsecure: false,
tlsConfig: &TLSConfig{
Enabled: true,
InsecureSkipVerify: false,
TrustSystemRootCAs: false,
CustomCertificates: []byte(invalidCertificate),
},
},
}

for _, tc := range testCases {
runConfigureTransportCredentialsTest(t, tc)
}
}
Loading
Loading