mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 15:13:58 -05:00
Add RPC HTTP options to specify custom truststore and password (#7978)
* Add RPC HTTP options to specify custom truststore and it's password * Update error logs to indicate options to use Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io> --------- Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io> Signed-off-by: Bhanu Pulluri <59369753+pullurib@users.noreply.github.com> Co-authored-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
This commit is contained in:
@@ -17,8 +17,10 @@
|
||||
- Fast Sync
|
||||
|
||||
### Additions and Improvements
|
||||
- Add RPC HTTP options to specify custom truststore and its password [#7978](https://github.com/hyperledger/besu/pull/7978)
|
||||
- Retrieve all transaction receipts for a block in one request [#6646](https://github.com/hyperledger/besu/pull/6646)
|
||||
|
||||
|
||||
### Bug fixes
|
||||
- Fix serialization of state overrides when `movePrecompileToAddress` is present [#8204](https://github.com/hyperledger/besu/pull/8024)
|
||||
|
||||
|
||||
@@ -176,6 +176,20 @@ public class JsonRpcHttpOptions {
|
||||
"Enable to accept clients certificate signed by a valid CA for client authentication (default: ${DEFAULT-VALUE})")
|
||||
private final Boolean isRpcHttpTlsCAClientsEnabled = false;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--rpc-http-tls-truststore-file"},
|
||||
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
|
||||
description = "Path to the truststore file for the JSON-RPC HTTP service.",
|
||||
arity = "1")
|
||||
private final Path rpcHttpTlsTruststoreFile = null;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--rpc-http-tls-truststore-password-file"},
|
||||
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
|
||||
description = "Path to the file containing the password for the truststore.",
|
||||
arity = "1")
|
||||
private final Path rpcHttpTlsTruststorePasswordFile = null;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--rpc-http-tls-protocol", "--rpc-http-tls-protocols"},
|
||||
description = "Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})",
|
||||
@@ -306,7 +320,6 @@ public class JsonRpcHttpOptions {
|
||||
jsonRpcConfiguration.setHost(
|
||||
Strings.isNullOrEmpty(rpcHttpHost) ? defaultHostAddress : rpcHttpHost);
|
||||
jsonRpcConfiguration.setHostsAllowlist(hostsAllowlist);
|
||||
;
|
||||
jsonRpcConfiguration.setHttpTimeoutSec(timoutSec);
|
||||
return jsonRpcConfiguration;
|
||||
}
|
||||
@@ -330,7 +343,18 @@ public class JsonRpcHttpOptions {
|
||||
commandLine,
|
||||
"--rpc-http-tls-client-auth-enabled",
|
||||
!isRpcHttpTlsClientAuthEnabled,
|
||||
asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled"));
|
||||
asList(
|
||||
"--rpc-http-tls-known-clients-file",
|
||||
"--rpc-http-tls-ca-clients-enabled",
|
||||
"--rpc-http-tls-truststore-file",
|
||||
"--rpc-http-tls-truststore-password-file"));
|
||||
|
||||
CommandLineUtils.checkOptionDependencies(
|
||||
logger,
|
||||
commandLine,
|
||||
"--rpc-http-tls-truststore-file",
|
||||
rpcHttpTlsTruststoreFile == null,
|
||||
asList("--rpc-http-tls-truststore-password-file"));
|
||||
}
|
||||
|
||||
private void checkRpcTlsOptionsDependencies(final Logger logger, final CommandLine commandLine) {
|
||||
@@ -392,12 +416,31 @@ public class JsonRpcHttpOptions {
|
||||
"File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint");
|
||||
}
|
||||
|
||||
if (isRpcHttpTlsClientAuthEnabled
|
||||
&& !isRpcHttpTlsCAClientsEnabled
|
||||
&& rpcHttpTlsKnownClientsFile == null) {
|
||||
throw new CommandLine.ParameterException(
|
||||
commandLine,
|
||||
"Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint");
|
||||
if (isRpcHttpTlsClientAuthEnabled) {
|
||||
if (!isRpcHttpTlsCAClientsEnabled
|
||||
&& rpcHttpTlsKnownClientsFile == null
|
||||
&& rpcHttpTlsTruststoreFile == null) {
|
||||
throw new CommandLine.ParameterException(
|
||||
commandLine,
|
||||
"Configuration error: TLS client authentication is enabled, but none of the following options are provided: "
|
||||
+ "1. Specify a known-clients file (--rpc-http-tls-known-clients-file) and/or Enable CA clients (--rpc-http-tls-ca-clients-enabled). "
|
||||
+ "2. Specify a truststore file and its password file (--rpc-http-tls-truststore-file and --rpc-http-tls-truststore-password-file). "
|
||||
+ "Only one of these options must be configured");
|
||||
}
|
||||
|
||||
if (rpcHttpTlsTruststoreFile != null && rpcHttpTlsTruststorePasswordFile == null) {
|
||||
throw new CommandLine.ParameterException(
|
||||
commandLine,
|
||||
"Configuration error: A truststore file is specified for JSON RPC HTTP endpoint, but the corresponding truststore password file (--rpc-http-tls-truststore-password-file) is missing");
|
||||
}
|
||||
|
||||
if ((isRpcHttpTlsCAClientsEnabled || rpcHttpTlsKnownClientsFile != null)
|
||||
&& rpcHttpTlsTruststoreFile != null) {
|
||||
throw new CommandLine.ParameterException(
|
||||
commandLine,
|
||||
"Configuration error: Truststore file (--rpc-http-tls-truststore-file) cannot be used together with CA clients (--rpc-http-tls-ca-clients-enabled) or a known-clients (--rpc-http-tls-known-clients-file) option. "
|
||||
+ "These options are mutually exclusive. Choose either truststore-based authentication or known-clients/CA clients configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols());
|
||||
@@ -441,10 +484,17 @@ public class JsonRpcHttpOptions {
|
||||
|
||||
private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() {
|
||||
if (isRpcHttpTlsClientAuthEnabled) {
|
||||
return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration()
|
||||
.withKnownClientsFile(rpcHttpTlsKnownClientsFile)
|
||||
.withCaClientsEnabled(isRpcHttpTlsCAClientsEnabled)
|
||||
.build();
|
||||
TlsClientAuthConfiguration.Builder tlsClientAuthConfigurationBuilder =
|
||||
TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration()
|
||||
.withKnownClientsFile(rpcHttpTlsKnownClientsFile)
|
||||
.withCaClientsEnabled(isRpcHttpTlsCAClientsEnabled)
|
||||
.withTruststorePath(rpcHttpTlsTruststoreFile);
|
||||
|
||||
if (rpcHttpTlsTruststorePasswordFile != null) {
|
||||
tlsClientAuthConfigurationBuilder.withTruststorePasswordSupplier(
|
||||
new FileBasedPasswordProvider(rpcHttpTlsTruststorePasswordFile));
|
||||
}
|
||||
return tlsClientAuthConfigurationBuilder.build();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -332,7 +332,10 @@ public class JsonRpcHttpOptionsTest extends CommandTestAbstract {
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty();
|
||||
assertThat(commandErrorOutput.toString(UTF_8))
|
||||
.contains(
|
||||
"Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint");
|
||||
"Configuration error: TLS client authentication is enabled, but none of the following options are provided: "
|
||||
+ "1. Specify a known-clients file (--rpc-http-tls-known-clients-file) and/or Enable CA clients (--rpc-http-tls-ca-clients-enabled). "
|
||||
+ "2. Specify a truststore file and its password file (--rpc-http-tls-truststore-file and --rpc-http-tls-truststore-password-file). "
|
||||
+ "Only one of these options must be configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -342,6 +345,7 @@ public class JsonRpcHttpOptionsTest extends CommandTestAbstract {
|
||||
final String keystoreFile = "/tmp/test.p12";
|
||||
final String keystorePasswordFile = "/tmp/test.txt";
|
||||
final String knownClientFile = "/tmp/knownClientFile";
|
||||
|
||||
parseCommand(
|
||||
"--rpc-http-enabled",
|
||||
"--rpc-http-host",
|
||||
@@ -422,6 +426,90 @@ public class JsonRpcHttpOptionsTest extends CommandTestAbstract {
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rpcHttpTlsClientAuthWithTrustStore() throws IOException {
|
||||
final String host = "1.2.3.4";
|
||||
final int port = 1234;
|
||||
final String keystoreFile = "/tmp/test.p12";
|
||||
final String keystorePasswordFile = "/tmp/test.txt";
|
||||
final String truststoreFile = "/tmp/truststore.p12";
|
||||
final String truststorePasswordFile = "/tmp/truststore.txt";
|
||||
|
||||
Files.writeString(Path.of(truststorePasswordFile), "password");
|
||||
parseCommand(
|
||||
"--rpc-http-enabled",
|
||||
"--rpc-http-host",
|
||||
host,
|
||||
"--rpc-http-port",
|
||||
String.valueOf(port),
|
||||
"--rpc-http-tls-enabled",
|
||||
"--rpc-http-tls-keystore-file",
|
||||
keystoreFile,
|
||||
"--rpc-http-tls-keystore-password-file",
|
||||
keystorePasswordFile,
|
||||
"--rpc-http-tls-client-auth-enabled",
|
||||
"--rpc-http-tls-truststore-file",
|
||||
truststoreFile,
|
||||
"--rpc-http-tls-truststore-password-file",
|
||||
truststorePasswordFile);
|
||||
|
||||
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
|
||||
verify(mockRunnerBuilder).build();
|
||||
|
||||
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
|
||||
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
|
||||
final Optional<TlsConfiguration> tlsConfiguration =
|
||||
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
|
||||
assertThat(tlsConfiguration.isPresent()).isTrue();
|
||||
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
|
||||
assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue();
|
||||
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().getTruststorePath())
|
||||
.isEqualTo(Optional.of(Path.of(truststoreFile)));
|
||||
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().getTrustStorePassword())
|
||||
.isEqualTo(Files.readString(Path.of(truststorePasswordFile)));
|
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty();
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rpcHttpTlsClientAuthWithTrustStoreAndKnownClientsFileReportsError()
|
||||
throws IOException {
|
||||
final String host = "1.2.3.4";
|
||||
final int port = 1234;
|
||||
final String keystoreFile = "/tmp/test.p12";
|
||||
final String keystorePasswordFile = "/tmp/test.txt";
|
||||
final String truststoreFile = "/tmp/truststore.p12";
|
||||
final String truststorePasswordFile = "/tmp/truststore.txt";
|
||||
final String knownClientFile = "/tmp/knownClientFile";
|
||||
|
||||
Files.writeString(Path.of(truststorePasswordFile), "password");
|
||||
parseCommand(
|
||||
"--rpc-http-enabled",
|
||||
"--rpc-http-host",
|
||||
host,
|
||||
"--rpc-http-port",
|
||||
String.valueOf(port),
|
||||
"--rpc-http-tls-enabled",
|
||||
"--rpc-http-tls-keystore-file",
|
||||
keystoreFile,
|
||||
"--rpc-http-tls-keystore-password-file",
|
||||
keystorePasswordFile,
|
||||
"--rpc-http-tls-client-auth-enabled",
|
||||
"--rpc-http-tls-truststore-file",
|
||||
truststoreFile,
|
||||
"--rpc-http-tls-truststore-password-file",
|
||||
truststorePasswordFile,
|
||||
"--rpc-http-tls-known-clients-file",
|
||||
knownClientFile);
|
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty();
|
||||
assertThat(commandErrorOutput.toString(UTF_8))
|
||||
.contains(
|
||||
"Configuration error: Truststore file (--rpc-http-tls-truststore-file) cannot be used together with CA clients (--rpc-http-tls-ca-clients-enabled) or a known-clients (--rpc-http-tls-known-clients-file) option. "
|
||||
+ "These options are mutually exclusive. Choose either truststore-based authentication or known-clients/CA clients configuration.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rpcHttpTlsClientAuthWithCAClientAndKnownClientFile() {
|
||||
final String host = "1.2.3.4";
|
||||
|
||||
@@ -84,6 +84,8 @@ rpc-http-tls-keystore-password-file="none.passwd"
|
||||
rpc-http-tls-client-auth-enabled=false
|
||||
rpc-http-tls-known-clients-file="rpc_tls_clients.txt"
|
||||
rpc-http-tls-ca-clients-enabled=false
|
||||
rpc-http-tls-truststore-file="none.pfx"
|
||||
rpc-http-tls-truststore-password-file="none.passwd"
|
||||
rpc-http-authentication-jwt-algorithm="RS256"
|
||||
rpc-ws-authentication-jwt-algorithm="RS256"
|
||||
rpc-http-tls-protocols=["TLSv1.2,TlSv1.1"]
|
||||
|
||||
@@ -429,7 +429,7 @@ public class JsonRpcHttpService {
|
||||
try {
|
||||
httpServerOptions
|
||||
.setSsl(true)
|
||||
.setPfxKeyCertOptions(
|
||||
.setKeyCertOptions(
|
||||
new PfxOptions()
|
||||
.setPath(tlsConfiguration.getKeyStorePath().toString())
|
||||
.setPassword(tlsConfiguration.getKeyStorePassword()))
|
||||
@@ -472,6 +472,14 @@ public class JsonRpcHttpService {
|
||||
httpServerOptions.setTrustOptions(
|
||||
allowlistClients(
|
||||
knownClientsFile, clientAuthConfiguration.isCaClientsEnabled())));
|
||||
clientAuthConfiguration
|
||||
.getTruststorePath()
|
||||
.ifPresent(
|
||||
truststorePath ->
|
||||
httpServerOptions.setTrustOptions(
|
||||
new PfxOptions()
|
||||
.setPath(truststorePath.toString())
|
||||
.setPassword(clientAuthConfiguration.getTrustStorePassword())));
|
||||
}
|
||||
|
||||
private String tlsLogMessage() {
|
||||
|
||||
@@ -17,15 +17,23 @@ package org.hyperledger.besu.ethereum.api.tls;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class TlsClientAuthConfiguration {
|
||||
private final Optional<Path> knownClientsFile;
|
||||
private final boolean caClientsEnabled;
|
||||
private final Optional<Path> truststorePath;
|
||||
private final Supplier<String> trustStorePasswordSupplier;
|
||||
|
||||
private TlsClientAuthConfiguration(
|
||||
final Optional<Path> knownClientsFile, final boolean caClientsEnabled) {
|
||||
final Optional<Path> knownClientsFile,
|
||||
final boolean caClientsEnabled,
|
||||
final Optional<Path> truststorePath,
|
||||
final Supplier<String> trustStorePasswordSupplier) {
|
||||
this.knownClientsFile = knownClientsFile;
|
||||
this.caClientsEnabled = caClientsEnabled;
|
||||
this.truststorePath = truststorePath;
|
||||
this.trustStorePasswordSupplier = trustStorePasswordSupplier;
|
||||
}
|
||||
|
||||
public Optional<Path> getKnownClientsFile() {
|
||||
@@ -36,9 +44,19 @@ public class TlsClientAuthConfiguration {
|
||||
return caClientsEnabled;
|
||||
}
|
||||
|
||||
public Optional<Path> getTruststorePath() {
|
||||
return truststorePath;
|
||||
}
|
||||
|
||||
public String getTrustStorePassword() {
|
||||
return trustStorePasswordSupplier.get();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private Path knownClientsFile;
|
||||
private boolean caClientsEnabled;
|
||||
private Path truststorePath;
|
||||
private Supplier<String> trustStorePasswordSupplier;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
@@ -56,12 +74,29 @@ public class TlsClientAuthConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruststorePath(final Path truststorePath) {
|
||||
this.truststorePath = truststorePath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTruststorePasswordSupplier(final Supplier<String> keyStorePasswordSupplier) {
|
||||
this.trustStorePasswordSupplier = keyStorePasswordSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TlsClientAuthConfiguration build() {
|
||||
if (!caClientsEnabled) {
|
||||
if (!caClientsEnabled && truststorePath == null) {
|
||||
Objects.requireNonNull(knownClientsFile, "Known Clients File is required");
|
||||
}
|
||||
if (!caClientsEnabled && knownClientsFile == null) {
|
||||
Objects.requireNonNull(truststorePath, "Truststore File is required");
|
||||
}
|
||||
|
||||
return new TlsClientAuthConfiguration(
|
||||
Optional.ofNullable(knownClientsFile), caClientsEnabled);
|
||||
Optional.ofNullable(knownClientsFile),
|
||||
caClientsEnabled,
|
||||
Optional.ofNullable(truststorePath),
|
||||
trustStorePasswordSupplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,13 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
|
||||
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
|
||||
import org.hyperledger.besu.nat.NatService;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -187,6 +189,37 @@ public class JsonRpcHttpServiceTlsClientAuthTest {
|
||||
return config;
|
||||
}
|
||||
|
||||
private Optional<TlsConfiguration> getRpcHttpTlsConfigurationOnlyWithTruststore() {
|
||||
final Path truststorePath = createTempFile();
|
||||
|
||||
// Create a new truststore and add the okHttpClientCertificate to it
|
||||
try (FileOutputStream truststoreOutputStream = new FileOutputStream(truststorePath.toFile())) {
|
||||
KeyStore truststore = KeyStore.getInstance("PKCS12");
|
||||
truststore.load(null, null);
|
||||
truststore.setCertificateEntry(
|
||||
"okHttpClientCertificate", okHttpClientCertificate.getCertificate());
|
||||
truststore.store(truststoreOutputStream, okHttpClientCertificate.getPassword());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create truststore", e);
|
||||
}
|
||||
|
||||
final FileBasedPasswordProvider trustStorePasswordProvider =
|
||||
new FileBasedPasswordProvider(createPasswordFile(okHttpClientCertificate));
|
||||
|
||||
final TlsConfiguration tlsConfiguration =
|
||||
aTlsConfiguration()
|
||||
.withKeyStorePath(besuCertificate.getKeyStoreFile())
|
||||
.withKeyStorePasswordSupplier(fileBasedPasswordProvider)
|
||||
.withClientAuthConfiguration(
|
||||
aTlsClientAuthConfiguration()
|
||||
.withTruststorePath(truststorePath)
|
||||
.withTruststorePasswordSupplier(trustStorePasswordProvider)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return Optional.of(tlsConfiguration);
|
||||
}
|
||||
|
||||
private Optional<TlsConfiguration> getRpcHttpTlsConfiguration() {
|
||||
final Path knownClientsFile = createTempFile();
|
||||
writeToKnownClientsFile(
|
||||
@@ -260,6 +293,23 @@ public class JsonRpcHttpServiceTlsClientAuthTest {
|
||||
netVersionSuccessful(this::getTlsHttpClient, baseUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void netVersionSuccessfulOnTlsWithClientCertInTruststore() throws Exception {
|
||||
|
||||
JsonRpcHttpService jsonRpcHttpService = null;
|
||||
try {
|
||||
jsonRpcHttpService =
|
||||
createJsonRpcHttpService(
|
||||
createJsonRpcConfig(this::getRpcHttpTlsConfigurationOnlyWithTruststore));
|
||||
jsonRpcHttpService.start().join();
|
||||
netVersionSuccessful(this::getTlsHttpClient, jsonRpcHttpService.url());
|
||||
} finally {
|
||||
if (jsonRpcHttpService != null) {
|
||||
jsonRpcHttpService.stop().join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void netVersionSuccessfulOnTlsWithClientCertAddedAsCA() throws Exception {
|
||||
netVersionSuccessful(this::getTlsHttpClientAddedAsCA, baseUrl);
|
||||
|
||||
Reference in New Issue
Block a user