Skip to content
Merged
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
11 changes: 11 additions & 0 deletions book/src/libs/seth.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ export SETH_LOG_LEVEL=info # global logger level
export SETH_CONFIG_PATH=seth.toml # path to the toml config
export SETH_NETWORK=Geth # selected network
export SETH_ROOT_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # root private key
export SETH_INSECURE_SKIP_VERIFY=false # set to true to skip TLS certificate verification for RPC connections (testing only!)

alias seth="SETH_CONFIG_PATH=seth.toml go run cmd/seth/seth.go" # useful alias for CLI
```
Expand Down Expand Up @@ -974,6 +975,16 @@ You need to pass a file with a list of transaction hashes to trace. The file sho
### RPC Traffic logging
With `SETH_LOG_LEVEL=trace` we will also log to console all traffic between Seth and RPC node. This can be useful for debugging as you can see all the requests and responses.

### TLS certificate verification
Seth verifies TLS certificates of HTTPS RPC endpoints. If you need to connect to an endpoint with a self-signed certificate in a testing environment, you can disable verification for a single network in the TOML config:
```toml
[[networks]]
name = "Anvil"
urls_secret = ["https://localhost:8545"]
insecure_skip_verify = true
```
or with `WithInsecureSkipVerify(true)` when building the client with `ClientBuilder`. Setting `SETH_INSECURE_SKIP_VERIFY=true` disables verification for all networks. Never use any of these with production RPC endpoints, as it allows anyone intercepting the traffic to return fake chain state.


### Read-only mode
It's possible to use Seth in read-only mode only for transaction confirmation and tracing. Following operations will fail:
Expand Down
3 changes: 3 additions & 0 deletions seth/.changeset/v1.52.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- **Breaking:** verify TLS certificates of RPC endpoints by default (previously `InsecureSkipVerify` was always enabled). Set `insecure_skip_verify = true` in the network TOML config, use `ClientBuilder.WithInsecureSkipVerify(true)` or set `SETH_INSECURE_SKIP_VERIFY=true` (all networks) to restore the old behaviour in testing environments with self-signed certificates.
- Add `WithInsecureSkipVerify` to `ClientBuilder` for programmatic usage
- RPC HTTP transport now honours `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` environment variables.
2 changes: 1 addition & 1 deletion seth/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func NewClientRaw(
cfg.MustFirstNetworkURL(),
rpc.WithHeaders(cfg.RPCHeaders),
rpc.WithHTTPClient(&http.Client{
Transport: NewLoggingTransport(),
Transport: newRPCTransport(cfg.Network.InsecureSkipVerify),
}),
)
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions seth/client_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,24 @@ func (c *ClientBuilder) WithHooks(hooks Hooks) *ClientBuilder {
return c
}

// WithInsecureSkipVerify disables TLS certificate verification for RPC connections.
// Use only in testing environments with self-signed certificates, never with production RPC endpoints.
// It sets `insecure_skip_verify` of the selected network, SETH_INSECURE_SKIP_VERIFY=true disables verification for all networks.
// Default value is false.
func (c *ClientBuilder) WithInsecureSkipVerify(insecureSkipVerify bool) *ClientBuilder {
if !c.checkIfNetworkIsSet() {
return c
}
c.config.Network.InsecureSkipVerify = insecureSkipVerify
// defensive programming
if len(c.config.Networks) == 0 {
c.config.Networks = append(c.config.Networks, c.config.Network)
} else if net := c.config.findNetworkByName(c.config.Network.Name); net != nil {
net.InsecureSkipVerify = insecureSkipVerify
}
return c
}

// WithReadOnlyMode sets the client to read-only mode. It removes all private keys from all Networks and disables nonce protection and ephemeral addresses.
func (c *ClientBuilder) WithReadOnlyMode() *ClientBuilder {
c.readonly = true
Expand Down
2 changes: 2 additions & 0 deletions seth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ type Network struct {
GasPriceEstimationBlocks uint64 `toml:"gas_price_estimation_blocks"`
GasPriceEstimationTxPriority string `toml:"gas_price_estimation_tx_priority"`
GasPriceEstimationAttemptCount uint `toml:"gas_price_estimation_attempt_count"`
// InsecureSkipVerify disables TLS certificate verification for RPC connections. Use only in testing environments.
InsecureSkipVerify bool `toml:"insecure_skip_verify"`
}

// DefaultClient returns a Client with reasonable default config with the specified RPC URL and private keys. You should pass at least 1 private key.
Expand Down
42 changes: 30 additions & 12 deletions seth/http_logging_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"time"
)

// InsecureSkipVerifyEnvVar disables TLS certificate verification for RPC connections when set to "true".
// Use only in testing environments with self-signed certificates.
const InsecureSkipVerifyEnvVar = "SETH_INSECURE_SKIP_VERIFY"

// LoggingTransport is a custom transport to log requests and responses
type LoggingTransport struct {
Transport http.RoundTripper
Expand Down Expand Up @@ -47,21 +51,35 @@ func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
return resp, nil
}

// NewLoggingTransport creates a new logging transport for GAP or default transport
// controlled by SETH_LOG_LEVEL
// NewLoggingTransport creates a new transport for RPC HTTP connections. TLS certificates are verified
// unless InsecureSkipVerifyEnvVar is set to "true". When SETH_LOG_LEVEL is "trace" requests and responses are logged.
func NewLoggingTransport() http.RoundTripper {
if strings.EqualFold(os.Getenv(LogLevelEnvVar), "trace") {
return &LoggingTransport{
// TODO: GAP, add proper certificates
Transport: &http.Transport{
//nolint
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
return newRPCTransport(false)
}

// newRPCTransport creates a transport for RPC HTTP connections. TLS certificate verification is disabled
// when insecureSkipVerify is true or InsecureSkipVerifyEnvVar is set to "true".
func newRPCTransport(insecureSkipVerify bool) http.RoundTripper {
var transport *http.Transport
if defaultTransport, ok := http.DefaultTransport.(*http.Transport); ok {
transport = defaultTransport.Clone()
} else {
// http.DefaultTransport was replaced with a custom RoundTripper, fall back to a transport with sane defaults
transport = &http.Transport{Proxy: http.ProxyFromEnvironment}
}

if insecureSkipVerify || strings.EqualFold(os.Getenv(InsecureSkipVerifyEnvVar), "true") {
L.Warn().
Msg("TLS certificate verification is disabled for RPC connections. Do not use this setting outside of testing environments")
transport.TLSClientConfig = &tls.Config{
//nolint:gosec // explicit opt-in for testing environments with self-signed certificates
InsecureSkipVerify: true,
}
}

return &http.Transport{
//nolint
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
if strings.EqualFold(os.Getenv(LogLevelEnvVar), "trace") {
return &LoggingTransport{Transport: transport}
}

return transport
}
95 changes: 95 additions & 0 deletions seth/http_logging_transport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package seth

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require"
)

func TestNewLoggingTransport_TLS(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

for _, logLevel := range []string{"info", "trace"} {
t.Run("untrusted certificate is rejected with log level "+logLevel, func(t *testing.T) {
t.Setenv(LogLevelEnvVar, logLevel)
client := &http.Client{Transport: NewLoggingTransport()}
resp, err := client.Get(server.URL)
if resp != nil {
_ = resp.Body.Close()
}
require.Error(t, err, "expected TLS verification to fail for untrusted certificate")
require.Contains(t, err.Error(), "certificate")
})

t.Run("untrusted certificate is accepted when verification is disabled with log level "+logLevel, func(t *testing.T) {
t.Setenv(LogLevelEnvVar, logLevel)
t.Setenv(InsecureSkipVerifyEnvVar, "true")
client := &http.Client{Transport: NewLoggingTransport()}
resp, err := client.Get(server.URL)
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
})

t.Run("untrusted certificate is accepted when verification is disabled via config with log level "+logLevel, func(t *testing.T) {
t.Setenv(LogLevelEnvVar, logLevel)
client := &http.Client{Transport: newRPCTransport(true)}
resp, err := client.Get(server.URL)
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
})
}

t.Run("custom http.DefaultTransport does not panic and still verifies certificates", func(t *testing.T) {
original := http.DefaultTransport
http.DefaultTransport = &LoggingTransport{Transport: original}
t.Cleanup(func() { http.DefaultTransport = original })

client := &http.Client{Transport: NewLoggingTransport()}
resp, err := client.Get(server.URL)
if resp != nil {
_ = resp.Body.Close()
}
require.Error(t, err, "expected TLS verification to fail for untrusted certificate")
require.Contains(t, err.Error(), "certificate")
})
}

func TestNetworkInsecureSkipVerify(t *testing.T) {
t.Run("is read from TOML network config", func(t *testing.T) {
var cfg Config
err := toml.Unmarshal([]byte(`
[[networks]]
name = "Secure"
urls_secret = ["https://rpc.example.com"]

[[networks]]
name = "SelfSigned"
urls_secret = ["https://localhost:8545"]
insecure_skip_verify = true
`), &cfg)
require.NoError(t, err)
require.Len(t, cfg.Networks, 2)
require.False(t, cfg.Networks[0].InsecureSkipVerify, "expected TLS verification to be enabled by default")
require.True(t, cfg.Networks[1].InsecureSkipVerify, "expected TLS verification to be disabled for SelfSigned network")
})

t.Run("is set on selected network by builder", func(t *testing.T) {
cfg, err := NewClientBuilder().
WithRpcUrl("https://localhost:8545").
WithInsecureSkipVerify(true).
WithReadOnlyMode().
BuildConfig()
require.NoError(t, err)
require.True(t, cfg.Network.InsecureSkipVerify)
require.Len(t, cfg.Networks, 1)
require.True(t, cfg.Networks[0].InsecureSkipVerify)
})
}
Loading