Compare commits

...

48 Commits

Author SHA1 Message Date
james-prysm
54482a7772 Merge branch 'develop' into web3signer-tls 2023-02-21 17:00:46 -06:00
james-prysm
8f9b2e990b Merge branch 'develop' into web3signer-tls 2023-02-20 17:20:59 -06:00
james-prysm
d37c777a30 fixing linting 2023-02-15 12:00:17 -06:00
james-prysm
00623ac8a7 Merge branch 'develop' into web3signer-tls 2023-02-15 11:57:26 -06:00
james-prysm
3c390027e2 removing unintended println 2023-02-15 11:35:36 -06:00
james-prysm
f025bafba5 Update testing/endtoend/static-files/certs/BUILD.bazel
Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
2023-02-15 11:32:14 -06:00
james-prysm
fc1d8a7d12 Merge branch 'develop' into web3signer-tls 2023-02-13 23:50:44 -06:00
james-prysm
0bdf731b53 Merge branch 'develop' into web3signer-tls 2023-02-13 10:02:18 -06:00
james-prysm
362b8868d2 Merge branch 'develop' into web3signer-tls 2023-02-09 08:36:36 -06:00
james-prysm
38ef97acef Merge branch 'develop' into web3signer-tls 2023-02-08 09:56:34 -06:00
james-prysm
9dcb458a18 Merge branch 'develop' into web3signer-tls 2023-01-31 17:58:47 -06:00
james-prysm
7617d86edd Merge branch 'develop' into web3signer-tls 2023-01-30 11:29:08 -06:00
Raul Jordan
d27705b3a3 Merge branch 'develop' into web3signer-tls 2023-01-27 11:59:14 -05:00
James He
bc39b42b0f adding https for public keys function 2023-01-18 21:59:26 -06:00
James He
4a78174fad fixing more unit tests 2023-01-18 21:11:20 -06:00
James He
7df6f9efbc fixing unit tests 2023-01-18 20:24:00 -06:00
James He
352b5a1085 allowing tests to view these files 2023-01-18 17:30:59 -06:00
James He
c0b1402df9 fixing bazel 2023-01-18 17:16:27 -06:00
James He
4f31fbc3c6 fixing unit tests 2023-01-18 16:55:30 -06:00
james-prysm
f5093d06bf Merge branch 'develop' into web3signer-tls 2023-01-18 15:58:18 -06:00
james-prysm
01d63502a1 Merge branch 'develop' into web3signer-tls 2023-01-18 15:24:38 -06:00
James He
3e1785e69a fixing linting 2023-01-18 15:24:08 -06:00
James He
4a0042af10 fixing unit tests 2023-01-18 15:15:13 -06:00
james-prysm
4f060cbec8 fixing bazel 2023-01-18 14:36:44 -06:00
james-prysm
301b869229 Merge branch 'web3signer-tls' of https://github.com/prysmaticlabs/prysm into web3signer-tls 2023-01-18 13:26:00 -06:00
james-prysm
79f9840c02 add fixes cert issues with web3signer 2023-01-18 13:23:15 -06:00
james-prysm
c24806be79 work in progress fixing cert issues 2023-01-17 17:09:05 -06:00
James He
b984e85a1d adding in both stderr and stdout logs for web3signer 2023-01-13 17:45:15 -06:00
james-prysm
2dfaf7103b Merge branch 'web3signer-tls' of https://github.com/prysmaticlabs/prysm into web3signer-tls 2023-01-13 17:04:37 -06:00
james-prysm
b84c5fc100 reverting some changes 2023-01-13 17:04:30 -06:00
james-prysm
494968fe05 Merge branch 'develop' into web3signer-tls 2023-01-13 17:02:52 -06:00
james-prysm
16f6b174b8 fixing bugs with setup to use certs, but not completely figured out 2023-01-13 16:58:48 -06:00
james-prysm
b6693530e1 Merge branch 'develop' into web3signer-tls 2023-01-12 18:36:46 -06:00
james-prysm
bb9de8ca10 fixing pathing 2023-01-12 18:21:54 -06:00
James He
ae42f65c27 changing flags for E2E web3signer 2023-01-12 14:34:39 -06:00
James He
16b63bbe6f renaming folder 2023-01-12 09:14:35 -06:00
james-prysm
4098215800 Merge branch 'develop' into web3signer-tls 2023-01-11 21:57:49 -06:00
James He
f81841b2a9 adding skip to key 2023-01-11 20:09:22 -06:00
James He
97b98d2b7b attempting to resolve deepsource issue 2023-01-11 17:34:19 -06:00
James He
2e8a872e91 reverting some changes and adding in new flags for p12 processing 2023-01-11 17:18:52 -06:00
james-prysm
5f8152921a Merge branch 'develop' into web3signer-tls 2023-01-11 14:09:46 -06:00
James He
1d5832c385 addressing deepsource issue 2023-01-11 08:19:37 -06:00
james-prysm
a0a5dff2c0 Merge branch 'develop' into web3signer-tls 2023-01-10 17:20:09 -06:00
James He
358e92c316 fixing more unit tests 2023-01-10 11:04:17 -06:00
James He
e98c373716 adding unit tests for certs 2023-01-10 10:58:31 -06:00
James He
3acb7d2c89 adding unit tests 2023-01-09 20:14:11 -06:00
James He
17970a8f8e adding tls cert requirements 2023-01-09 17:12:09 -06:00
James He
37fdb340cc first step to adding tls 2023-01-09 16:15:51 -06:00
21 changed files with 388 additions and 38 deletions

View File

@@ -51,6 +51,7 @@ var (
Name: "tls-cert",
Usage: "Certificate for secure gRPC. Pass this and the tls-key flag in order to use gRPC securely.",
}
// EnableRPCFlag enables controlling the validator client via gRPC (without web UI).
EnableRPCFlag = &cli.BoolFlag{
Name: "rpc",
@@ -264,6 +265,7 @@ var (
Usage: "Disables TLS when connecting to a remote signer. (WARNING! This will result in insecure requests!)",
Value: false,
}
// RemoteSignerCertPathFlag defines the path to a client.crt file for a wallet to connect to
// a secure signer via TLS and gRPC.
RemoteSignerCertPathFlag = &cli.StringFlag{
@@ -303,6 +305,24 @@ var (
Usage: "comma separated list of public keys OR an external url endpoint for the validator to retrieve public keys from for usage with web3signer",
}
// Web3SignerClientCertFLag is used to specify the PKCS12 client certificate and key in PFX (.p12) format.
Web3SignerClientCertFLag = &cli.StringFlag{
Name: "validators-external-signer-client-cert",
Usage: "PKCS12 client cert and key in PFX (.p12 format. web3signer only supports PKCS12 ",
}
// Web3SignerClientCertPasswordFlag is used to specify the password for the PKCS12 client certificate specified in the "validators-external-signer-client-cert" flag.
Web3SignerClientCertPasswordFlag = &cli.StringFlag{
Name: "validators-external-signer-client-cert-password",
Usage: "path to password for the provided PKCS12 client cert from --validators-external-signer-client-cert",
}
// Web3SignerCACertFLag is used to specify the CA certificate in .pem format.
Web3SignerCACertFLag = &cli.StringFlag{
Name: "validators-external-signer-ca-cert",
Usage: "CA cert used in .pem format",
}
// KeymanagerKindFlag defines the kind of keymanager desired by a user during wallet creation.
KeymanagerKindFlag = &cli.StringFlag{
Name: "keymanager-kind",

View File

@@ -75,6 +75,9 @@ var appFlags = []cli.Flag{
// Consensys' Web3Signer flags
flags.Web3SignerURLFlag,
flags.Web3SignerPublicValidatorKeysFlag,
flags.Web3SignerCACertFLag,
flags.Web3SignerClientCertPasswordFlag,
flags.Web3SignerClientCertFLag,
flags.SuggestedFeeRecipientFlag,
flags.ProposerSettingsURLFlag,
flags.ProposerSettingsFlag,

View File

@@ -108,6 +108,9 @@ var appHelpFlagGroups = []flagGroup{
flags.GraffitiFileFlag,
flags.Web3SignerURLFlag,
flags.Web3SignerPublicValidatorKeysFlag,
flags.Web3SignerCACertFLag,
flags.Web3SignerClientCertPasswordFlag,
flags.Web3SignerClientCertFLag,
flags.ProposerSettingsFlag,
flags.ProposerSettingsURLFlag,
flags.SuggestedFeeRecipientFlag,

View File

@@ -14,6 +14,7 @@ go_library(
"web3remotesigner.go",
],
data = [
"//testing/endtoend/static-files/certs:web3signer-certs",
"//testing/endtoend/static-files/eth1:eth1data",
"@lighthouse//:lighthouse_bin",
],
@@ -45,6 +46,7 @@ go_library(
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
"@in_gopkg_yaml_v2//:go_default_library",
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
"@org_golang_x_crypto//pkcs12:go_default_library",
"@org_golang_x_sync//errgroup:go_default_library",
],
)

View File

@@ -313,7 +313,6 @@ func (node *BeaconNode) Start(ctx context.Context) error {
}
node.peerID = peerId
}
// Mark node as ready.
close(node.started)

View File

@@ -253,10 +253,17 @@ func (v *ValidatorNode) Start(ctx context.Context) error {
args = append(args, features.E2EValidatorFlags...)
}
if v.config.UseWeb3RemoteSigner {
args = append(args, fmt.Sprintf("--%s=http://localhost:%d", flags.Web3SignerURLFlag.Name, Web3RemoteSignerPort))
args = append(args, fmt.Sprintf("--%s=https://localhost:%d", flags.Web3SignerURLFlag.Name, Web3RemoteSignerPort))
// Write the pubkeys as comma separated hex strings with 0x prefix.
// See: https://docs.teku.consensys.net/en/latest/HowTo/External-Signer/Use-External-Signer/
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerPublicValidatorKeysFlag.Name, strings.Join(validatorHexPubKeys, ",")))
pa, err := bazel.RunfilesPath()
if err != nil {
return err
}
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerClientCertFLag.Name, pa+"/testing/endtoend/static-files/certs/prysm_client_identity.p12"))
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerClientCertPasswordFlag.Name, pa+"/testing/endtoend/static-files/certs/pass.txt"))
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerCACertFLag.Name, pa+"/testing/endtoend/static-files/certs/cacerts.pem"))
} else {
// When not using remote key signer, use interop keys.
args = append(args,

View File

@@ -2,8 +2,11 @@ package components
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
@@ -15,6 +18,8 @@ import (
"syscall"
"time"
"golang.org/x/crypto/pkcs12"
"github.com/bazelbuild/rules_go/go/tools/bazel"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
@@ -80,13 +85,21 @@ func (w *Web3RemoteSigner) Start(ctx context.Context) error {
// A file path to yaml config file is acceptable network argument.
network = testDir
}
pa, err := bazel.RunfilesPath()
if err != nil {
return err
}
args := []string{
// Global flags
fmt.Sprintf("--key-store-path=%s", keystorePath),
fmt.Sprintf("--data-path=%s", websignerDataDir),
fmt.Sprintf("--http-listen-port=%d", Web3RemoteSignerPort),
"--logging=ALL",
fmt.Sprintf("--tls-keystore-file=%s", pa+"/testing/endtoend/static-files/certs/web3signer_keystore.p12"),
fmt.Sprintf("--tls-keystore-password-file=%s", pa+"/testing/endtoend/static-files/certs/pass.txt"),
//"--tls-allow-any-client=true",
//"--tls-allow-ca-clients=true",
fmt.Sprintf("--tls-known-clients-file=%s", pa+"/testing/endtoend/static-files/certs/knownClients.txt"),
// Command
"eth2",
// Command flags
@@ -97,6 +110,7 @@ func (w *Web3RemoteSigner) Start(ctx context.Context) error {
cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Test code is safe to do this.
w.cmd = cmd
// Write stderr to log files.
stderr, err := os.Create(path.Join(e2e.TestParams.LogPath, "web3signer.stderr.log"))
if err != nil {
@@ -108,17 +122,70 @@ func (w *Web3RemoteSigner) Start(ctx context.Context) error {
}
}()
cmd.Stderr = stderr
// Write stdout to log files.
stdout, err := os.Create(path.Join(e2e.TestParams.LogPath, "web3signer.stdout.log"))
if err != nil {
return err
}
defer func() {
if err := stdout.Close(); err != nil {
log.WithError(err).Error("Failed to close stdout file")
}
}()
cmd.Stdout = stdout
log.Infof("Starting web3signer with flags: %s %s", binaryPath, strings.Join(args, " "))
if err = cmd.Start(); err != nil {
return err
}
go w.monitorStart()
tlsConfig, err := getTLSconfig(pa)
if err != nil {
return err
}
go w.monitorStart(tlsConfig)
return cmd.Wait()
}
func getTLSconfig(runpath string) (*tls.Config, error) {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}
p12, err := os.ReadFile(filepath.Clean(runpath + "/testing/endtoend/static-files/certs/prysm_client_identity.p12"))
if err != nil {
return nil, err
}
p12pass, err := os.ReadFile(filepath.Clean(runpath + "/testing/endtoend/static-files/certs/pass.txt"))
if err != nil {
return nil, err
}
blocks, err := pkcs12.ToPEM(p12, string(p12pass))
if err != nil {
return nil, errors.Wrap(err, "pkcs12 to PEM conversion failed")
}
var pemData []byte
for _, b := range blocks {
pemData = append(pemData, pem.EncodeToMemory(b)...)
}
clientPair, err := tls.X509KeyPair(pemData, pemData)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain client's certificate and/or key")
}
tlsConfig.Certificates = []tls.Certificate{clientPair}
cp := x509.NewCertPool()
pemc, err := os.ReadFile(filepath.Clean(runpath + "/testing/endtoend/static-files/certs/cacerts.pem"))
if err != nil {
return nil, err
}
cp.AppendCertsFromPEM(pemc)
tlsConfig.RootCAs = cp
return tlsConfig, nil
}
func (w *Web3RemoteSigner) Started() <-chan struct{} {
return w.started
}
@@ -139,15 +206,17 @@ func (w *Web3RemoteSigner) Stop() error {
}
// monitorStart by polling server until it returns a 200 at /upcheck.
func (w *Web3RemoteSigner) monitorStart() {
client := &http.Client{}
func (w *Web3RemoteSigner) monitorStart(config *tls.Config) {
client := &http.Client{Transport: &http.Transport{TLSClientConfig: config}}
for {
req, err := http.NewRequestWithContext(w.ctx, "GET", fmt.Sprintf("http://localhost:%d/upcheck", Web3RemoteSignerPort), nil)
req, err := http.NewRequestWithContext(w.ctx, "GET", fmt.Sprintf("https://localhost:%d/upcheck", Web3RemoteSignerPort), nil)
if err != nil {
panic(err)
}
res, err := client.Do(req)
_ = err
if err != nil {
log.WithError(err).Error("Web3signer upcheck failed, attempting again until time out...")
}
if res != nil && res.StatusCode == 200 {
close(w.started)
return
@@ -170,9 +239,21 @@ func (w *Web3RemoteSigner) wait(ctx context.Context) {
// PublicKeys queries the web3signer and returns the response keys.
func (w *Web3RemoteSigner) PublicKeys(ctx context.Context) ([]bls.PublicKey, error) {
w.wait(ctx)
pa, err := bazel.RunfilesPath()
if err != nil {
return nil, err
}
tlsConfig, err := getTLSconfig(pa)
if err != nil {
return nil, err
}
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost:%d/api/v1/eth2/publicKeys", Web3RemoteSignerPort), nil)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://localhost:%d/api/v1/eth2/publicKeys", Web3RemoteSignerPort), nil)
if err != nil {
return nil, err
}

View File

@@ -6,10 +6,10 @@ lighthouse_archive_name = "lighthouse-%s-x86_64-unknown-linux-gnu-portable.tar.g
def e2e_deps():
http_archive(
name = "web3signer",
urls = ["https://artifacts.consensys.net/public/web3signer/raw/names/web3signer.tar.gz/versions/22.8.1/web3signer-22.8.1.tar.gz"],
sha256 = "ec888222484c4d1b6203bd6d248890adf713f8bf47fb362fb36e8d47a98cb401",
urls = ["https://artifacts.consensys.net/public/web3signer/raw/names/web3signer.tar.gz/versions/23.1.0/web3signer-23.1.0.tar.gz"],
sha256 = "47b6ff266d0c99185e4a4a595bc2e0578f0f722fb08baa80df462938f2f8fe74",
build_file = "@prysm//testing/endtoend:web3signer.BUILD",
strip_prefix = "web3signer-22.8.1",
strip_prefix = "web3signer-23.1.0",
)
http_archive(

View File

@@ -0,0 +1,12 @@
filegroup(
name = "web3signer-certs",
testonly = True,
srcs = [
"cacerts.pem",
"knownClients.txt",
"pass.txt",
"prysm_client_identity.p12",
"web3signer_keystore.p12",
],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIEAaZvuDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJy
b290Lm15Y29tcGFueS5jb20wHhcNMjMwMTE3MjIyNDAyWhcNMjMwNDE3MjIyNDAy
WjAdMRswGQYDVQQDExJyb290Lm15Y29tcGFueS5jb20wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCEDj2ZTxcd5rfm/ULK+b2jGjR3aaDh8HCLs3DV9ZPx
N7VyWduEkE6mIJqTw0inZS1d9czVgH5klY5yZ5Xw6vKcrbN0JXm8TrShkpzfGADu
9enKqyk97XtHALqx6Ie1IB/agmeMiwPr5il7Y/1EFu2f9ocfE+2mBhvhjGEo9USm
EtsXAjsSAivCU1szALFBk2rrU8GPG6945PmD7no9YfAJNYPfLCG8zOTUPK/oBmXm
+NhZ+kbNP0PsgfgrKI6fC2qxQ9HI6GyDidSHvntSVq5RSi33PwFc2zfjMWOb1MXR
fnoPp4Z1M9NSxxScZ4DNnTaPWm+gvfUuEo5jvuQ1Gn8hAgMBAAGjVDBSMB0GA1Ud
DgQWBBSG533gVFLJywsRI8boDs2fUm26MTAgBgNVHREEGTAXgglsb2NhbGhvc3SH
BH8AAAGHBAoAAAUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA
fVJc1FCUjBQ/MY6D5j4o2JYAxu1Gl4EFN/dOhUzhnvUihz3DfRzlxKLHoMCG+2Ew
e3HoMIrHBJskp6zw3UawlrBikS6HNther1DXpz5HshSmqBKUAxAI4/Dg74z8kyUt
GsEB0vwWX5m9ErIGmsaYKK86ucIDgQeyK3SOxHy2Ob1kmjluox9RoJGH9PU0FoxV
D+9PSypqh7dFaiCiiTMxPJW7cpEkF9DDLvXIwHOigNkglB32pUaURIYVtqgd7RzM
7FdqCSHTlAZskyBm+Vu8B1ez7JUfDyRVVaXNPoea/4y1Csj8bQrcBfw9d8uO9uLi
tLArdWb0jpS3jjwCWIjfrw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDKzCCAhOgAwIBAgIEdnJ86zANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJy
b290Lm15Y29tcGFueS5jb20wHhcNMjMwMTE3MjIyNDU2WhcNMjMwNDE3MjIyNDU2
WjAbMRkwFwYDVQQDExBjYS5teWNvbXBhbnkuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAl0zBNXzYWIZS5zF74EXdFAuyh7l8K6GBQTDvAczbKQoX
tPrANE1mHhbqLYGFrtThEaNxFxsN/mzatp88L6yw/LdMviCvYTPUEvai0tWe3ATc
i43o6GI8/0Yvp8s0vSd5STuSR3B7pvrK3G1RVAr8ZmwKFaDJvxgYiYEItpra3U9+
AdRmQV6WRH/QRLs0xA8rAJMIUPvHzYJSiUHNop1FaOnWEEsH+dQhax0GGrSOGvcC
GTRPKY8exoZ4W/UYs/Entc0vdVKch0T3OkWeSOqEXBz2drTVah7gjbJ6cB54BM9t
6wy0gZE8AD3jUbZTTfMw9aa5MCtoKUqXss+cp/dctwIDAQABo3UwczAdBgNVHQ4E
FgQUCxe24+bRbxpjNrMlfVT8P43Qv2kwIAYDVR0RBBkwF4IJbG9jYWxob3N0hwR/
AAABhwQKAAAFMA8GA1UdEwQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUhud94FRSycsL
ESPG6A7Nn1JtujEwDQYJKoZIhvcNAQELBQADggEBABGqa3RncfzhfwbH1dYiBPrs
EahzGvNfgkcvZGWYy2j/uo5HL3O0TnvpiZKo5mcAvGQvtMXFVU27dK+Prk0LBLLJ
TXlT7H788SVRgt0HJec/NNvnzOA72F4S9w33eyazGCcafx9Zr4vf+jTtQbh995wT
QhLuSQ4yIVBUMDLoq+1gjeEnj7nMatNgQ3ThWYE7sl7EOip7ZTLt7p3LCEDkzHvC
yhQKcaQ454k0HW4OLNrdEIkFiuBHVEV67ufZ+Vpunr9GftpUddCPXiU0MXayMo95
Nmsv4ojp9w3xPH0oIUadJT8YM50Ui6KcvJ6cIcJZFCE29Yq6nl7hzeGTsz/twu4=
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
prysm DB:6D:20:46:3A:54:56:18:BB:EC:FF:BA:9F:55:5D:BF:A9:A2:96:BC:2F:8B:5F:9A:D8:4D:13:EF:BF:D7:90:5A

View File

@@ -0,0 +1 @@
changeit

View File

@@ -29,12 +29,16 @@ go_library(
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_x_crypto//pkcs12:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["keymanager_test.go"],
data = [
"//testing/endtoend/static-files/certs:web3signer-certs",
],
embed = [":go_default_library"],
deps = [
"//config/fieldparams:go_default_library",
@@ -42,10 +46,10 @@ go_test(
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/keymanager/remote-web3signer/internal:go_default_library",
"//validator/keymanager/remote-web3signer/v1/mock:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
],
)

View File

@@ -28,8 +28,8 @@ go_test(
srcs = ["client_test.go"],
deps = [
":go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
],
)

View File

@@ -3,6 +3,7 @@ package internal
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
@@ -46,8 +47,25 @@ type ApiClient struct {
RestClient *http.Client
}
// ApiClientOpt is a functional option for the Client type (http.Client wrapper)
type ApiClientOpt func(*ApiClient)
// WithTls Enables two-way TLS on the API using the provided cert information.
func WithTls(config *tls.Config) ApiClientOpt {
return func(c *ApiClient) {
if c.RestClient.Transport == nil {
c.RestClient.Transport = &http.Transport{TLSClientConfig: config}
} else {
if tpt, ok := c.RestClient.Transport.(*http.Transport); ok {
tpt.TLSClientConfig = config
}
}
}
}
// NewApiClient method instantiates a new ApiClient object.
func NewApiClient(baseEndpoint string) (*ApiClient, error) {
func NewApiClient(baseEndpoint string, opts ...ApiClientOpt) (*ApiClient, error) {
u, err := url.ParseRequestURI(baseEndpoint)
if err != nil {
return nil, errors.Wrap(err, "invalid format, unable to parse url")
@@ -55,10 +73,14 @@ func NewApiClient(baseEndpoint string) (*ApiClient, error) {
if u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("web3signer url must be in the format of http(s)://host:port url used: %v", baseEndpoint)
}
return &ApiClient{
c := &ApiClient{
BaseURL: u,
RestClient: &http.Client{},
}, nil
}
for _, o := range opts {
o(c)
}
return c, nil
}
// Sign is a wrapper method around the web3signer sign api.

View File

@@ -11,9 +11,9 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/internal"
"github.com/stretchr/testify/assert"
)
// mockTransport is the mock Transport object
@@ -47,8 +47,8 @@ func TestClient_Sign_HappyPath(t *testing.T) {
assert.NoError(t, err)
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", jsonRequest)
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
assert.NoError(t, err)
assert.Equal(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
}
func TestClient_Sign_HappyPath_Jsontype(t *testing.T) {
@@ -76,8 +76,8 @@ func TestClient_Sign_HappyPath_Jsontype(t *testing.T) {
assert.NoError(t, err)
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", jsonRequest)
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
assert.NoError(t, err)
assert.Equal(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
}
func TestClient_Sign_500(t *testing.T) {
@@ -95,7 +95,7 @@ func TestClient_Sign_500(t *testing.T) {
assert.NoError(t, err)
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", jsonRequest)
assert.NotNil(t, err)
assert.Nil(t, resp)
assert.Equal(t, nil, resp)
}
@@ -114,7 +114,7 @@ func TestClient_Sign_412(t *testing.T) {
assert.NoError(t, err)
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", jsonRequest)
assert.NotNil(t, err)
assert.Nil(t, resp)
assert.Equal(t, nil, resp)
}
@@ -133,7 +133,7 @@ func TestClient_Sign_400(t *testing.T) {
assert.NoError(t, err)
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", jsonRequest)
assert.NotNil(t, err)
assert.Nil(t, resp)
assert.Equal(t, nil, resp)
}
@@ -151,9 +151,9 @@ func TestClient_GetPublicKeys_HappyPath(t *testing.T) {
cl := internal.ApiClient{BaseURL: u, RestClient: &http.Client{Transport: mock}}
resp, err := cl.GetPublicKeys(context.Background(), "example.com/api/publickeys")
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.NoError(t, err)
// we would like them as 48byte base64 without 0x
assert.EqualValues(t, "[162 181 170 173 156 110 254 254 123 185 177 36 58 4 52 4 243 54 41 55 207 182 179 24 51 146 152 51 23 63 71 102 48 234 44 254 176 217 221 241 95 151 202 134 133 148 136 32]", fmt.Sprintf("%v", resp[0][:]))
assert.Equal(t, "[162 181 170 173 156 110 254 254 123 185 177 36 58 4 52 4 243 54 41 55 207 182 179 24 51 146 152 51 23 63 71 102 48 234 44 254 176 217 221 241 95 151 202 134 133 148 136 32]", fmt.Sprintf("%v", resp[0][:]))
}
func TestClient_GetPublicKeys_EncodingError(t *testing.T) {
@@ -170,7 +170,7 @@ func TestClient_GetPublicKeys_EncodingError(t *testing.T) {
cl := internal.ApiClient{BaseURL: u, RestClient: &http.Client{Transport: mock}}
resp, err := cl.GetPublicKeys(context.Background(), "example.com/api/publickeys")
assert.Equal(t, err.Error(), "failed to decode from Hex from the following public key index locations: 0, 1, 2, ")
assert.Nil(t, resp)
assert.Equal(t, 0, len(resp))
}
// TODO: not really in use, should be revisited
@@ -183,7 +183,7 @@ func TestClient_ReloadSignerKeys_HappyPath(t *testing.T) {
assert.NoError(t, err)
cl := internal.ApiClient{BaseURL: u, RestClient: &http.Client{Transport: mock}}
err = cl.ReloadSignerKeys(context.Background())
assert.Nil(t, err)
assert.NoError(t, err)
}
// TODO: not really in use, should be revisited
@@ -199,5 +199,5 @@ func TestClient_GetServerStatus_HappyPath(t *testing.T) {
cl := internal.ApiClient{BaseURL: u, RestClient: &http.Client{Transport: mock}}
resp, err := cl.GetServerStatus(context.Background())
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.NoError(t, err)
}

View File

@@ -3,8 +3,12 @@ package remote_web3signer
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -22,6 +26,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/internal"
web3signerv1 "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/v1"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/pkcs12"
)
// SetupConfig includes configuration values for initializing.
@@ -40,6 +45,10 @@ type SetupConfig struct {
// a static list of public keys to be passed by the user to determine what accounts should sign.
// This will provide a layer of safety against slashing if the web3signer is shared across validators.
ProvidedPublicKeys [][48]byte
ClientCertPath string
ClientCertPasswordPath string
CACertPath string
}
// Keymanager defines the web3signer keymanager.
@@ -58,7 +67,16 @@ func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
if cfg.BaseEndpoint == "" || !bytesutil.IsValidRoot(cfg.GenesisValidatorsRoot) {
return nil, fmt.Errorf("invalid setup config, one or more configs are empty: BaseEndpoint: %v, GenesisValidatorsRoot: %#x", cfg.BaseEndpoint, cfg.GenesisValidatorsRoot)
}
client, err := internal.NewApiClient(cfg.BaseEndpoint)
options := make([]internal.ApiClientOpt, 0)
tlsOpt, err := configureTLSOpt(cfg)
if err != nil {
return nil, err
}
if tlsOpt != nil {
options = append(options, tlsOpt)
}
client, err := internal.NewApiClient(cfg.BaseEndpoint, options...)
if err != nil {
return nil, errors.Wrap(err, "could not create apiClient")
}
@@ -73,6 +91,60 @@ func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
}, nil
}
func configureTLSOpt(cfg *SetupConfig) (internal.ApiClientOpt, error) {
if cfg.ClientCertPath != "" || cfg.ClientCertPasswordPath != "" || cfg.CACertPath != "" {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}
if cfg.ClientCertPath != "" || cfg.ClientCertPasswordPath != "" {
if cfg.ClientCertPath == "" {
return nil, errors.New("PKCS12 client certificate is required")
}
if cfg.ClientCertPasswordPath == "" {
return nil, errors.New("PKCS12 client certificate password is required")
}
p12, err := os.ReadFile(filepath.Clean(cfg.ClientCertPath))
if err != nil {
return nil, err
}
p12pass, err := os.ReadFile(filepath.Clean(cfg.ClientCertPasswordPath))
if err != nil {
return nil, err
}
blocks, err := pkcs12.ToPEM(p12, string(p12pass))
if err != nil {
return nil, errors.Wrap(err, "pkcs12 to PEM conversion failed")
}
var pemData []byte
for _, b := range blocks {
pemData = append(pemData, pem.EncodeToMemory(b)...)
}
clientPair, err := tls.X509KeyPair(pemData, pemData)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain client's certificate and/or key")
}
tlsConfig.Certificates = []tls.Certificate{clientPair}
}
cp := x509.NewCertPool()
// Load the CA for the server certificate if present.
if cfg.CACertPath != "" {
serverCA, err := os.ReadFile(cfg.CACertPath)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain server's CA certificate")
}
if !cp.AppendCertsFromPEM(serverCA) {
return nil, errors.Wrap(err, "failed to add server's CA certificate to pool")
}
}
tlsConfig.RootCAs = cp
return internal.WithTls(tlsConfig), nil
}
return nil, nil
}
// FetchValidatingPublicKeys fetches the validating public keys
// from the remote server or from the provided keys if there are no existing public keys set
// or provides the existing keys in the keymanager.

View File

@@ -13,10 +13,10 @@ import (
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v3/proto/eth/service"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/internal"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/v1/mock"
"github.com/stretchr/testify/assert"
)
type MockClient struct {
@@ -212,8 +212,8 @@ func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithKeyList(t *testing.T
fmt.Printf("error: %v", err)
}
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, resp, keys)
assert.NoError(t, err)
require.DeepEqual(t, resp, keys)
}
func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithExternalURL(t *testing.T) {
@@ -247,8 +247,8 @@ func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithExternalURL(t *testi
fmt.Printf("error: %v", err)
}
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, resp, keys)
assert.NoError(t, err)
require.DeepEqual(t, resp, keys)
}
func TestKeymanager_FetchValidatingPublicKeys_WithExternalURL_ThrowsError(t *testing.T) {
@@ -273,7 +273,7 @@ func TestKeymanager_FetchValidatingPublicKeys_WithExternalURL_ThrowsError(t *tes
km.client = client
resp, err := km.FetchValidatingPublicKeys(ctx)
assert.NotNil(t, err)
assert.Nil(t, resp)
assert.Equal(t, 0, len(resp))
assert.Equal(t, "could not get public keys from remote server url: http://example2.com/api/v1/eth2/publicKeys: mock error", fmt.Sprintf("%v", err))
}
@@ -345,3 +345,82 @@ func TestKeymanager_DeletePublicKeys(t *testing.T) {
require.Equal(t, ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND, status.Status)
}
}
func TestNewKeymanager_Certificates(t *testing.T) {
// web3signer only supports PKCS12
base := "../../../testing/endtoend/static-files/certs/"
clientcert := "prysm_client_identity.p12"
cacert := "cacerts.pem"
tests := []struct {
name string
opts *SetupConfig
err string
}{
{
name: "skips tls",
opts: &SetupConfig{},
},
{
name: "NoClientPass",
opts: &SetupConfig{
ClientCertPath: base + clientcert,
},
err: "PKCS12 client certificate password is required",
},
{
name: "happy path tls activated",
opts: &SetupConfig{
ClientCertPath: base + clientcert,
ClientCertPasswordPath: base + "pass.txt",
CACertPath: base + cacert,
},
},
{
name: "BadClientCert",
opts: &SetupConfig{
ClientCertPath: base + "pass.txt",
ClientCertPasswordPath: base + "pass.txt",
},
err: "pkcs12 to PEM conversion failed",
},
{
name: "BadClientPassword",
opts: &SetupConfig{
ClientCertPath: base + clientcert,
ClientCertPasswordPath: base + cacert,
},
err: "pkcs12 to PEM conversion failed: pkcs12: decryption password incorrect",
},
{
name: "MissingCACert",
opts: &SetupConfig{
ClientCertPath: base + clientcert,
ClientCertPasswordPath: base + "pass.txt",
CACertPath: `bad`,
},
err: "failed to obtain server's CA certificate: open bad: no such file or directory",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
if err != nil {
fmt.Printf("error: %v", err)
}
_, err = NewKeymanager(context.Background(), &SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
PublicKeysURL: "http://example2.com/api/v1/eth2/publicKeys",
ClientCertPath: test.opts.ClientCertPath,
ClientCertPasswordPath: test.opts.ClientCertPasswordPath,
CACertPath: test.opts.CACertPath,
})
if test.err == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, test.err, err)
}
})
}
}

View File

@@ -456,6 +456,12 @@ func Web3SignerConfig(cliCtx *cli.Context) (*remoteweb3signer.SetupConfig, error
BaseEndpoint: u.String(),
GenesisValidatorsRoot: nil,
}
// set up TLS if required
web3signerConfig.ClientCertPath = cliCtx.String(flags.Web3SignerClientCertFLag.Name)
web3signerConfig.ClientCertPasswordPath = cliCtx.String(flags.Web3SignerClientCertPasswordFlag.Name)
web3signerConfig.CACertPath = cliCtx.String(flags.Web3SignerCACertFLag.Name)
if cliCtx.IsSet(flags.WalletPasswordFileFlag.Name) {
log.Warnf("%s was provided while using web3signer and will be ignored", flags.WalletPasswordFileFlag.Name)
}