mirror of https://github.com/hyperledger/besu
commit
6620e1d951
@ -0,0 +1,492 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.cli.options.stable; |
||||||
|
|
||||||
|
import static java.util.Arrays.asList; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_PRETTY_JSON_ENABLED; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS; |
||||||
|
|
||||||
|
import org.hyperledger.besu.cli.DefaultCommandValues; |
||||||
|
import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; |
||||||
|
import org.hyperledger.besu.cli.custom.RpcAuthFileValidator; |
||||||
|
import org.hyperledger.besu.cli.util.CommandLineUtils; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; |
||||||
|
import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider; |
||||||
|
import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.security.KeyManagementException; |
||||||
|
import java.security.NoSuchAlgorithmException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Predicate; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
import javax.net.ssl.SSLContext; |
||||||
|
import javax.net.ssl.SSLEngine; |
||||||
|
|
||||||
|
import com.google.common.base.Strings; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import picocli.CommandLine; |
||||||
|
|
||||||
|
/** |
||||||
|
* Handles configuration options for the JSON-RPC HTTP service, including validation and creation of |
||||||
|
* a JSON-RPC configuration. |
||||||
|
*/ |
||||||
|
public class JsonRpcHttpOptions { |
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-enabled"}, |
||||||
|
description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") |
||||||
|
private final Boolean isRpcHttpEnabled = false; |
||||||
|
|
||||||
|
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-host"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, |
||||||
|
description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||||
|
arity = "1") |
||||||
|
private String rpcHttpHost; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-port"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, |
||||||
|
description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||||
|
arity = "1") |
||||||
|
private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-max-active-connections"}, |
||||||
|
description = |
||||||
|
"Maximum number of HTTP connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", |
||||||
|
arity = "1") |
||||||
|
private final Integer rpcHttpMaxConnections = DefaultCommandValues.DEFAULT_HTTP_MAX_CONNECTIONS; |
||||||
|
|
||||||
|
// A list of origins URLs that are accepted by the JsonRpcHttpServer (CORS)
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-cors-origins"}, |
||||||
|
description = "Comma separated origin domain URLs for CORS validation (default: none)") |
||||||
|
private final CorsAllowedOriginsProperty rpcHttpCorsAllowedOrigins = |
||||||
|
new CorsAllowedOriginsProperty(); |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-api", "--rpc-http-apis"}, |
||||||
|
paramLabel = "<api name>", |
||||||
|
split = " {0,1}, {0,1}", |
||||||
|
arity = "1..*", |
||||||
|
description = |
||||||
|
"Comma separated list of APIs to enable on JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") |
||||||
|
private final List<String> rpcHttpApis = DEFAULT_RPC_APIS; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-api-method-no-auth", "--rpc-http-api-methods-no-auth"}, |
||||||
|
paramLabel = "<api name>", |
||||||
|
split = " {0,1}, {0,1}", |
||||||
|
arity = "1..*", |
||||||
|
description = |
||||||
|
"Comma separated list of API methods to exclude from RPC authentication services, RPC HTTP authentication must be enabled") |
||||||
|
private final List<String> rpcHttpApiMethodsNoAuth = new ArrayList<String>(); |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-authentication-enabled"}, |
||||||
|
description = |
||||||
|
"Require authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") |
||||||
|
private final Boolean isRpcHttpAuthenticationEnabled = false; |
||||||
|
|
||||||
|
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-authentication-credentials-file"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||||
|
description = |
||||||
|
"Storage file for JSON-RPC HTTP authentication credentials (default: ${DEFAULT-VALUE})", |
||||||
|
arity = "1") |
||||||
|
private String rpcHttpAuthenticationCredentialsFile = null; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-authentication-jwt-public-key-file"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||||
|
description = "JWT public key file for JSON-RPC HTTP authentication", |
||||||
|
arity = "1") |
||||||
|
private final File rpcHttpAuthenticationPublicKeyFile = null; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-authentication-jwt-algorithm"}, |
||||||
|
description = |
||||||
|
"Encryption algorithm used for HTTP JWT public key. Possible values are ${COMPLETION-CANDIDATES}" |
||||||
|
+ " (default: ${DEFAULT-VALUE})", |
||||||
|
arity = "1") |
||||||
|
private final JwtAlgorithm rpcHttpAuthenticationAlgorithm = |
||||||
|
DefaultCommandValues.DEFAULT_JWT_ALGORITHM; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-enabled"}, |
||||||
|
description = "Enable TLS for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") |
||||||
|
private final Boolean isRpcHttpTlsEnabled = false; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-keystore-file"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||||
|
description = |
||||||
|
"Keystore (PKCS#12) containing key/certificate for the JSON-RPC HTTP service. Required if TLS is enabled.") |
||||||
|
private final Path rpcHttpTlsKeyStoreFile = null; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-keystore-password-file"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||||
|
description = |
||||||
|
"File containing password to unlock keystore for the JSON-RPC HTTP service. Required if TLS is enabled.") |
||||||
|
private final Path rpcHttpTlsKeyStorePasswordFile = null; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-client-auth-enabled"}, |
||||||
|
description = |
||||||
|
"Enable TLS client authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") |
||||||
|
private final Boolean isRpcHttpTlsClientAuthEnabled = false; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-known-clients-file"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||||
|
description = |
||||||
|
"Path to file containing clients certificate common name and fingerprint for client authentication") |
||||||
|
private final Path rpcHttpTlsKnownClientsFile = null; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-ca-clients-enabled"}, |
||||||
|
description = |
||||||
|
"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-protocol", "--rpc-http-tls-protocols"}, |
||||||
|
description = "Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})", |
||||||
|
split = ",", |
||||||
|
arity = "1..*") |
||||||
|
private final List<String> rpcHttpTlsProtocols = |
||||||
|
new ArrayList<>(DefaultCommandValues.DEFAULT_TLS_PROTOCOLS); |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-tls-cipher-suite", "--rpc-http-tls-cipher-suites"}, |
||||||
|
description = "Comma separated list of TLS cipher suites to support", |
||||||
|
split = ",", |
||||||
|
arity = "1..*") |
||||||
|
private final List<String> rpcHttpTlsCipherSuites = new ArrayList<>(); |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-max-batch-size"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP, |
||||||
|
description = |
||||||
|
"Specifies the maximum number of requests in a single RPC batch request via RPC. -1 specifies no limit (default: ${DEFAULT-VALUE})") |
||||||
|
private final Integer rpcHttpMaxBatchSize = DefaultCommandValues.DEFAULT_HTTP_MAX_BATCH_SIZE; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--rpc-http-max-request-content-length"}, |
||||||
|
paramLabel = DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP, |
||||||
|
description = "Specifies the maximum request content length. (default: ${DEFAULT-VALUE})") |
||||||
|
private final Long rpcHttpMaxRequestContentLength = |
||||||
|
DefaultCommandValues.DEFAULT_MAX_REQUEST_CONTENT_LENGTH; |
||||||
|
|
||||||
|
@CommandLine.Option( |
||||||
|
names = {"--json-pretty-print-enabled"}, |
||||||
|
description = "Enable JSON pretty print format (default: ${DEFAULT-VALUE})") |
||||||
|
private final Boolean prettyJsonEnabled = DEFAULT_PRETTY_JSON_ENABLED; |
||||||
|
|
||||||
|
/** |
||||||
|
* Validates the Rpc Http options. |
||||||
|
* |
||||||
|
* @param logger Logger instance |
||||||
|
* @param commandLine CommandLine instance |
||||||
|
* @param configuredApis Predicate for configured APIs |
||||||
|
*/ |
||||||
|
public void validate( |
||||||
|
final Logger logger, final CommandLine commandLine, final Predicate<String> configuredApis) { |
||||||
|
|
||||||
|
if (!rpcHttpApis.stream().allMatch(configuredApis)) { |
||||||
|
final List<String> invalidHttpApis = new ArrayList<>(rpcHttpApis); |
||||||
|
invalidHttpApis.removeAll(VALID_APIS); |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, |
||||||
|
"Invalid value for option '--rpc-http-api': invalid entries found " + invalidHttpApis); |
||||||
|
} |
||||||
|
|
||||||
|
final boolean validHttpApiMethods = |
||||||
|
rpcHttpApiMethodsNoAuth.stream().allMatch(RpcMethod::rpcMethodExists); |
||||||
|
|
||||||
|
if (!validHttpApiMethods) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, |
||||||
|
"Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); |
||||||
|
} |
||||||
|
|
||||||
|
if (isRpcHttpAuthenticationEnabled) { |
||||||
|
CommandLineUtils.checkOptionDependencies( |
||||||
|
logger, |
||||||
|
commandLine, |
||||||
|
"--rpc-http-authentication-public-key-file", |
||||||
|
rpcHttpAuthenticationPublicKeyFile == null, |
||||||
|
List.of("--rpc-http-authentication-jwt-algorithm")); |
||||||
|
} |
||||||
|
|
||||||
|
if (isRpcHttpAuthenticationEnabled |
||||||
|
&& rpcHttpAuthenticationCredentialsFile(commandLine) == null |
||||||
|
&& rpcHttpAuthenticationPublicKeyFile == null) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, |
||||||
|
"Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); |
||||||
|
} |
||||||
|
|
||||||
|
checkDependencies(logger, commandLine); |
||||||
|
|
||||||
|
if (isRpcTlsConfigurationRequired()) { |
||||||
|
validateTls(commandLine); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a JsonRpcConfiguration based on the provided options. |
||||||
|
* |
||||||
|
* @param hostsAllowlist List of hosts allowed |
||||||
|
* @param defaultHostAddress Default host address |
||||||
|
* @param timoutSec timeout in seconds |
||||||
|
* @return A JsonRpcConfiguration instance |
||||||
|
*/ |
||||||
|
public JsonRpcConfiguration jsonRpcConfiguration( |
||||||
|
final List<String> hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { |
||||||
|
|
||||||
|
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); |
||||||
|
jsonRpcConfiguration.setEnabled(isRpcHttpEnabled); |
||||||
|
jsonRpcConfiguration.setHost( |
||||||
|
Strings.isNullOrEmpty(rpcHttpHost) ? defaultHostAddress : rpcHttpHost); |
||||||
|
jsonRpcConfiguration.setPort(rpcHttpPort); |
||||||
|
jsonRpcConfiguration.setMaxActiveConnections(rpcHttpMaxConnections); |
||||||
|
jsonRpcConfiguration.setCorsAllowedDomains(rpcHttpCorsAllowedOrigins); |
||||||
|
jsonRpcConfiguration.setRpcApis(rpcHttpApis.stream().distinct().collect(Collectors.toList())); |
||||||
|
jsonRpcConfiguration.setNoAuthRpcApis( |
||||||
|
rpcHttpApiMethodsNoAuth.stream().distinct().collect(Collectors.toList())); |
||||||
|
jsonRpcConfiguration.setHostsAllowlist(hostsAllowlist); |
||||||
|
jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled); |
||||||
|
jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile); |
||||||
|
jsonRpcConfiguration.setAuthenticationPublicKeyFile(rpcHttpAuthenticationPublicKeyFile); |
||||||
|
jsonRpcConfiguration.setAuthenticationAlgorithm(rpcHttpAuthenticationAlgorithm); |
||||||
|
jsonRpcConfiguration.setTlsConfiguration(rpcHttpTlsConfiguration()); |
||||||
|
jsonRpcConfiguration.setHttpTimeoutSec(timoutSec); |
||||||
|
jsonRpcConfiguration.setMaxBatchSize(rpcHttpMaxBatchSize); |
||||||
|
jsonRpcConfiguration.setMaxRequestContentLength(rpcHttpMaxRequestContentLength); |
||||||
|
jsonRpcConfiguration.setPrettyJsonEnabled(prettyJsonEnabled); |
||||||
|
return jsonRpcConfiguration; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks dependencies between options. |
||||||
|
* |
||||||
|
* @param logger Logger instance |
||||||
|
* @param commandLine CommandLine instance |
||||||
|
*/ |
||||||
|
public void checkDependencies(final Logger logger, final CommandLine commandLine) { |
||||||
|
checkRpcTlsClientAuthOptionsDependencies(logger, commandLine); |
||||||
|
checkRpcTlsOptionsDependencies(logger, commandLine); |
||||||
|
checkRpcHttpOptionsDependencies(logger, commandLine); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkRpcTlsClientAuthOptionsDependencies( |
||||||
|
final Logger logger, final CommandLine commandLine) { |
||||||
|
CommandLineUtils.checkOptionDependencies( |
||||||
|
logger, |
||||||
|
commandLine, |
||||||
|
"--rpc-http-tls-client-auth-enabled", |
||||||
|
!isRpcHttpTlsClientAuthEnabled, |
||||||
|
asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled")); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkRpcTlsOptionsDependencies(final Logger logger, final CommandLine commandLine) { |
||||||
|
CommandLineUtils.checkOptionDependencies( |
||||||
|
logger, |
||||||
|
commandLine, |
||||||
|
"--rpc-http-tls-enabled", |
||||||
|
!isRpcHttpTlsEnabled, |
||||||
|
asList( |
||||||
|
"--rpc-http-tls-keystore-file", |
||||||
|
"--rpc-http-tls-keystore-password-file", |
||||||
|
"--rpc-http-tls-client-auth-enabled", |
||||||
|
"--rpc-http-tls-known-clients-file", |
||||||
|
"--rpc-http-tls-ca-clients-enabled", |
||||||
|
"--rpc-http-tls-protocols", |
||||||
|
"--rpc-http-tls-cipher-suite", |
||||||
|
"--rpc-http-tls-cipher-suites")); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkRpcHttpOptionsDependencies(final Logger logger, final CommandLine commandLine) { |
||||||
|
CommandLineUtils.checkOptionDependencies( |
||||||
|
logger, |
||||||
|
commandLine, |
||||||
|
"--rpc-http-enabled", |
||||||
|
!isRpcHttpEnabled, |
||||||
|
asList( |
||||||
|
"--rpc-http-api", |
||||||
|
"--rpc-http-apis", |
||||||
|
"--rpc-http-api-method-no-auth", |
||||||
|
"--rpc-http-api-methods-no-auth", |
||||||
|
"--rpc-http-cors-origins", |
||||||
|
"--rpc-http-host", |
||||||
|
"--rpc-http-port", |
||||||
|
"--rpc-http-max-active-connections", |
||||||
|
"--rpc-http-authentication-enabled", |
||||||
|
"--rpc-http-authentication-credentials-file", |
||||||
|
"--rpc-http-authentication-public-key-file", |
||||||
|
"--rpc-http-tls-enabled", |
||||||
|
"--rpc-http-tls-keystore-file", |
||||||
|
"--rpc-http-tls-keystore-password-file", |
||||||
|
"--rpc-http-tls-client-auth-enabled", |
||||||
|
"--rpc-http-tls-known-clients-file", |
||||||
|
"--rpc-http-tls-ca-clients-enabled", |
||||||
|
"--rpc-http-authentication-jwt-algorithm", |
||||||
|
"--rpc-http-tls-protocols", |
||||||
|
"--rpc-http-tls-cipher-suite", |
||||||
|
"--rpc-http-tls-cipher-suites")); |
||||||
|
} |
||||||
|
|
||||||
|
private void validateTls(final CommandLine commandLine) { |
||||||
|
if (rpcHttpTlsKeyStoreFile == null) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, "Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); |
||||||
|
} |
||||||
|
|
||||||
|
if (rpcHttpTlsKeyStorePasswordFile == null) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, |
||||||
|
"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"); |
||||||
|
} |
||||||
|
|
||||||
|
rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols()); |
||||||
|
if (rpcHttpTlsProtocols.isEmpty()) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, |
||||||
|
"No valid TLS protocols specified (the following protocols are enabled: " |
||||||
|
+ getJDKEnabledProtocols() |
||||||
|
+ ")"); |
||||||
|
} |
||||||
|
|
||||||
|
for (final String cipherSuite : rpcHttpTlsCipherSuites) { |
||||||
|
if (!getJDKEnabledCipherSuites().contains(cipherSuite)) { |
||||||
|
throw new CommandLine.ParameterException( |
||||||
|
commandLine, "Invalid TLS cipher suite specified " + cipherSuite); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<TlsConfiguration> rpcHttpTlsConfiguration() { |
||||||
|
if (!isRpcTlsConfigurationRequired()) { |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
|
||||||
|
rpcHttpTlsCipherSuites.retainAll(getJDKEnabledCipherSuites()); |
||||||
|
|
||||||
|
return Optional.of( |
||||||
|
TlsConfiguration.Builder.aTlsConfiguration() |
||||||
|
.withKeyStorePath(rpcHttpTlsKeyStoreFile) |
||||||
|
.withKeyStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(rpcHttpTlsKeyStorePasswordFile)) |
||||||
|
.withClientAuthConfiguration(rpcHttpTlsClientAuthConfiguration()) |
||||||
|
.withSecureTransportProtocols(rpcHttpTlsProtocols) |
||||||
|
.withCipherSuites(rpcHttpTlsCipherSuites) |
||||||
|
.build()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isRpcTlsConfigurationRequired() { |
||||||
|
return isRpcHttpEnabled && isRpcHttpTlsEnabled; |
||||||
|
} |
||||||
|
|
||||||
|
private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() { |
||||||
|
if (isRpcHttpTlsClientAuthEnabled) { |
||||||
|
return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration() |
||||||
|
.withKnownClientsFile(rpcHttpTlsKnownClientsFile) |
||||||
|
.withCaClientsEnabled(isRpcHttpTlsCAClientsEnabled) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<String> getJDKEnabledCipherSuites() { |
||||||
|
try { |
||||||
|
final SSLContext context = SSLContext.getInstance("TLS"); |
||||||
|
context.init(null, null, null); |
||||||
|
final SSLEngine engine = context.createSSLEngine(); |
||||||
|
return Arrays.asList(engine.getEnabledCipherSuites()); |
||||||
|
} catch (final KeyManagementException | NoSuchAlgorithmException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static List<String> getJDKEnabledProtocols() { |
||||||
|
try { |
||||||
|
final SSLContext context = SSLContext.getInstance("TLS"); |
||||||
|
context.init(null, null, null); |
||||||
|
final SSLEngine engine = context.createSSLEngine(); |
||||||
|
return Arrays.asList(engine.getEnabledProtocols()); |
||||||
|
} catch (final KeyManagementException | NoSuchAlgorithmException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String rpcHttpAuthenticationCredentialsFile(final CommandLine commandLine) { |
||||||
|
final String filename = rpcHttpAuthenticationCredentialsFile; |
||||||
|
|
||||||
|
if (filename != null) { |
||||||
|
RpcAuthFileValidator.validate(commandLine, filename, "HTTP"); |
||||||
|
} |
||||||
|
return filename; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the list of APIs enabled for RPC over HTTP. |
||||||
|
* |
||||||
|
* @return A list of APIs |
||||||
|
*/ |
||||||
|
public List<String> getRpcHttpApis() { |
||||||
|
return rpcHttpApis; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the port for RPC over HTTP. |
||||||
|
* |
||||||
|
* @return The port number |
||||||
|
*/ |
||||||
|
public Integer getRpcHttpPort() { |
||||||
|
return rpcHttpPort; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if RPC over HTTP is enabled. |
||||||
|
* |
||||||
|
* @return true if enabled, false otherwise |
||||||
|
*/ |
||||||
|
public Boolean isRpcHttpEnabled() { |
||||||
|
return isRpcHttpEnabled; |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,926 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.cli.options; |
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM; |
||||||
|
import static org.mockito.Mockito.times; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.verifyNoInteractions; |
||||||
|
|
||||||
|
import org.hyperledger.besu.cli.CommandTestAbstract; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; |
||||||
|
import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; |
||||||
|
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.net.ServerSocket; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||||
|
import org.mockito.Mockito; |
||||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class) |
||||||
|
public class JsonRpcHttpOptionsTest extends CommandTestAbstract { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpEnabledPropertyMustBeUsed() { |
||||||
|
parseCommand("--rpc-http-enabled"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcApisPropertyMustBeUsed() { |
||||||
|
parseCommand("--rpc-http-api", "ETH,NET,PERM", "--rpc-http-enabled"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
verify(mockLogger) |
||||||
|
.warn("Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) |
||||||
|
.containsExactlyInAnyOrder(ETH.name(), NET.name(), PERM.name()); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcApisPropertyIgnoresDuplicatesAndMustBeUsed() { |
||||||
|
parseCommand("--rpc-http-api", "ETH,NET,NET", "--rpc-http-enabled"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) |
||||||
|
.containsExactlyInAnyOrder(ETH.name(), NET.name()); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcApiNoAuthMethodsIgnoresDuplicatesAndMustBeUsed() { |
||||||
|
parseCommand( |
||||||
|
"--rpc-http-api-methods-no-auth", |
||||||
|
"admin_peers, admin_peers, eth_getWork", |
||||||
|
"--rpc-http-enabled"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getNoAuthRpcApis()) |
||||||
|
.containsExactlyInAnyOrder( |
||||||
|
RpcMethod.ADMIN_PEERS.getMethodName(), RpcMethod.ETH_GET_WORK.getMethodName()); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpNoAuthApiMethodsCannotBeInvalid() { |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-api-method-no-auth", "invalid"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains( |
||||||
|
"Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpOptionsRequiresServiceToBeEnabled() { |
||||||
|
parseCommand( |
||||||
|
"--rpc-http-api", |
||||||
|
"ETH,NET", |
||||||
|
"--rpc-http-host", |
||||||
|
"0.0.0.0", |
||||||
|
"--rpc-http-port", |
||||||
|
"1234", |
||||||
|
"--rpc-http-cors-origins", |
||||||
|
"all", |
||||||
|
"--rpc-http-max-active-connections", |
||||||
|
"88"); |
||||||
|
|
||||||
|
verifyOptionsConstraintLoggerCall( |
||||||
|
"--rpc-http-enabled", |
||||||
|
"--rpc-http-host", |
||||||
|
"--rpc-http-port", |
||||||
|
"--rpc-http-cors-origins", |
||||||
|
"--rpc-http-api", |
||||||
|
"--rpc-http-max-active-connections"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpOptionsRequiresServiceToBeEnabledToml() throws IOException { |
||||||
|
final Path toml = |
||||||
|
createTempFile( |
||||||
|
"toml", |
||||||
|
"rpc-http-api=[\"ETH\",\"NET\"]\n" |
||||||
|
+ "rpc-http-host=\"0.0.0.0\"\n" |
||||||
|
+ "rpc-http-port=1234\n" |
||||||
|
+ "rpc-http-cors-origins=[\"all\"]\n" |
||||||
|
+ "rpc-http-max-active-connections=88"); |
||||||
|
|
||||||
|
parseCommand("--config-file", toml.toString()); |
||||||
|
|
||||||
|
verifyOptionsConstraintLoggerCall( |
||||||
|
"--rpc-http-enabled", |
||||||
|
"--rpc-http-host", |
||||||
|
"--rpc-http-port", |
||||||
|
"--rpc-http-cors-origins", |
||||||
|
"--rpc-http-api", |
||||||
|
"--rpc-http-max-active-connections"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpHostAndPortOptionsMustBeUsed() { |
||||||
|
|
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
parseCommand( |
||||||
|
"--rpc-http-enabled", "--rpc-http-host", host, "--rpc-http-port", String.valueOf(port)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpHostMayBeLocalhost() { |
||||||
|
|
||||||
|
final String host = "localhost"; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-host", host); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpHostMayBeIPv6() { |
||||||
|
|
||||||
|
final String host = "2600:DB8::8545"; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-host", host); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() { |
||||||
|
final int maxConnections = 99; |
||||||
|
parseCommand("--rpc-http-max-active-connections", String.valueOf(maxConnections)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) |
||||||
|
.isEqualTo(maxConnections); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsRequiresRpcHttpEnabled() { |
||||||
|
parseCommand("--rpc-http-tls-enabled"); |
||||||
|
|
||||||
|
verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsRequiresRpcHttpEnabledToml() throws IOException { |
||||||
|
final Path toml = createTempFile("toml", "rpc-http-tls-enabled=true\n"); |
||||||
|
|
||||||
|
parseCommand("--config-file", toml.toString()); |
||||||
|
|
||||||
|
verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsWithoutKeystoreReportsError() { |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-tls-enabled"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsWithoutPasswordfileReportsError() { |
||||||
|
parseCommand( |
||||||
|
"--rpc-http-enabled", |
||||||
|
"--rpc-http-tls-enabled", |
||||||
|
"--rpc-http-tls-keystore-file", |
||||||
|
"/tmp/test.p12"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains( |
||||||
|
"File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsKeystoreAndPasswordMustBeUsed() { |
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
final String keystoreFile = "/tmp/test.p12"; |
||||||
|
final String keystorePasswordFile = "/tmp/test.txt"; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
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().isEmpty()).isTrue(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsClientAuthWithoutKnownFileReportsError() { |
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
final String keystoreFile = "/tmp/test.p12"; |
||||||
|
final String keystorePasswordFile = "/tmp/test.txt"; |
||||||
|
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"); |
||||||
|
|
||||||
|
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"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsClientAuthWithKnownClientFile() { |
||||||
|
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 knownClientFile = "/tmp/knownClientFile"; |
||||||
|
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-known-clients-file", |
||||||
|
knownClientFile); |
||||||
|
|
||||||
|
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().getKnownClientsFile().get()) |
||||||
|
.isEqualTo(Path.of(knownClientFile)); |
||||||
|
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) |
||||||
|
.isFalse(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsClientAuthWithCAClient() { |
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
final String keystoreFile = "/tmp/test.p12"; |
||||||
|
final String keystorePasswordFile = "/tmp/test.txt"; |
||||||
|
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-ca-clients-enabled"); |
||||||
|
|
||||||
|
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() |
||||||
|
.getKnownClientsFile() |
||||||
|
.isEmpty()) |
||||||
|
.isTrue(); |
||||||
|
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) |
||||||
|
.isTrue(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsClientAuthWithCAClientAndKnownClientFile() { |
||||||
|
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 knownClientFile = "/tmp/knownClientFile"; |
||||||
|
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-ca-clients-enabled", |
||||||
|
"--rpc-http-tls-known-clients-file", |
||||||
|
knownClientFile); |
||||||
|
|
||||||
|
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().getKnownClientsFile().get()) |
||||||
|
.isEqualTo(Path.of(knownClientFile)); |
||||||
|
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) |
||||||
|
.isTrue(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsCheckDefaultProtocolsAndCipherSuites() { |
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
final String keystoreFile = "/tmp/test.p12"; |
||||||
|
final String keystorePasswordFile = "/tmp/test.txt"; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
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(); |
||||||
|
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); |
||||||
|
assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); |
||||||
|
assertThat(tlsConfiguration.get().getCipherSuites().get()).isEmpty(); |
||||||
|
assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) |
||||||
|
.containsExactly("TLSv1.3", "TLSv1.2"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsCheckInvalidProtocols() { |
||||||
|
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 protocol = "TLsv1.4"; |
||||||
|
|
||||||
|
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-protocols", |
||||||
|
protocol); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).contains("No valid TLS protocols specified"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsCheckInvalidCipherSuites() { |
||||||
|
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 cipherSuites = "Invalid"; |
||||||
|
|
||||||
|
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-cipher-suites", |
||||||
|
cipherSuites); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Invalid TLS cipher suite specified " + cipherSuites); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsCheckValidProtocolsAndCipherSuites() { |
||||||
|
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 protocols = "TLSv1.3,TLSv1.2"; |
||||||
|
final String cipherSuites = |
||||||
|
"TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; |
||||||
|
|
||||||
|
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-protocols", |
||||||
|
protocols, |
||||||
|
"--rpc-http-tls-cipher-suites", |
||||||
|
cipherSuites); |
||||||
|
|
||||||
|
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(); |
||||||
|
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); |
||||||
|
assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); |
||||||
|
assertThat(tlsConfiguration.get().getCipherSuites().get()) |
||||||
|
.containsExactlyInAnyOrder( |
||||||
|
"TLS_AES_256_GCM_SHA384", |
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", |
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); |
||||||
|
assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) |
||||||
|
.containsExactlyInAnyOrder("TLSv1.2", "TLSv1.3"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpTlsWarnIfCipherSuitesSpecifiedWithoutTls() { |
||||||
|
final String host = "1.2.3.4"; |
||||||
|
final int port = 1234; |
||||||
|
final String cipherSuites = "Invalid"; |
||||||
|
|
||||||
|
parseCommand( |
||||||
|
"--rpc-http-enabled", |
||||||
|
"--engine-rpc-enabled", |
||||||
|
"--rpc-http-host", |
||||||
|
host, |
||||||
|
"--rpc-http-port", |
||||||
|
String.valueOf(port), |
||||||
|
"--rpc-http-tls-cipher-suite", |
||||||
|
cipherSuites); |
||||||
|
verify( |
||||||
|
mockLogger, |
||||||
|
times(2)) // this is verified for both the full suite of apis, and the engine group.
|
||||||
|
.warn( |
||||||
|
"{} has been ignored because {} was not defined on the command line.", |
||||||
|
"--rpc-http-tls-cipher-suite", |
||||||
|
"--rpc-http-tls-enabled"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsTwoDomainsMustBuildListWithBothDomains() { |
||||||
|
final String[] origins = {"http://domain1.com", "https://domain2.com"}; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) |
||||||
|
.isEqualTo(origins); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsDoubleCommaFilteredOut() { |
||||||
|
final String[] origins = {"http://domain1.com", "https://domain2.com"}; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",,", origins)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) |
||||||
|
.isEqualTo(origins); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsWithWildcardMustBuildListWithWildcard() { |
||||||
|
final String[] origins = {"*"}; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) |
||||||
|
.isEqualTo(origins); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsWithAllMustBuildListWithWildcard() { |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", "all"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).containsExactly("*"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsWithNoneMustBuildEmptyList() { |
||||||
|
final String[] origins = {"none"}; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).isEmpty(); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFail() { |
||||||
|
final String[] origins = {"http://domain1.com", "none"}; |
||||||
|
parseCommand("--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Value 'none' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFailNoneFirst() { |
||||||
|
final String[] origins = {"none", "http://domain1.com"}; |
||||||
|
parseCommand("--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Value 'none' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsAllWithAnotherDomainMustFail() { |
||||||
|
parseCommand("--rpc-http-cors-origins=http://domain1.com,all"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Values '*' or 'all' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsAllWithAnotherDomainMustFailAsFlags() { |
||||||
|
parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=all"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Values '*' or 'all' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFail() { |
||||||
|
parseCommand("--rpc-http-cors-origins=http://domain1.com,*"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Values '*' or 'all' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFailAsFlags() { |
||||||
|
parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=*"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Values '*' or 'all' can't be used with other domains"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsInvalidRegexShouldFail() { |
||||||
|
final String[] origins = {"**"}; |
||||||
|
parseCommand("--rpc-http-cors-origins", String.join(",", origins)); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Domain values result in invalid regex pattern"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpCorsOriginsEmptyValueFails() { |
||||||
|
parseCommand("--rpc-http-cors-origins="); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Domain cannot be empty string or null string."); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcApisPropertyWithInvalidEntryMustDisplayError() { |
||||||
|
parseCommand("--rpc-http-api", "BOB"); |
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
// PicoCLI uses longest option name for message when option has multiple names, so here plural.
|
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Invalid value for option '--rpc-http-api': invalid entries found [BOB]"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcApisPropertyWithPluginNamespaceAreValid() { |
||||||
|
|
||||||
|
rpcEndpointServiceImpl.registerRPCEndpoint( |
||||||
|
"bob", "method", (Function<PluginRpcRequest, Object>) request -> "nothing"); |
||||||
|
|
||||||
|
parseCommand("--rpc-http-api", "BOB"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) |
||||||
|
.containsExactlyInAnyOrder("BOB"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpMaxRequestContentLengthOptionMustBeUsed() { |
||||||
|
final int rpcHttpMaxRequestContentLength = 1; |
||||||
|
parseCommand( |
||||||
|
"--rpc-http-max-request-content-length", Long.toString(rpcHttpMaxRequestContentLength)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxRequestContentLength()) |
||||||
|
.isEqualTo(rpcHttpMaxRequestContentLength); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rpcHttpMaxBatchSizeOptionMustBeUsed() { |
||||||
|
final int rpcHttpMaxBatchSize = 1; |
||||||
|
parseCommand("--rpc-http-max-batch-size", Integer.toString(rpcHttpMaxBatchSize)); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxBatchSize()) |
||||||
|
.isEqualTo(rpcHttpMaxBatchSize); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void portInUseReportsError() throws IOException { |
||||||
|
final ServerSocket serverSocket = new ServerSocket(8545); |
||||||
|
|
||||||
|
parseCommandWithPortCheck("--rpc-http-enabled"); |
||||||
|
|
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains("Port(s) '[8545]' already in use. Check for other processes using the port(s)."); |
||||||
|
|
||||||
|
serverSocket.close(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void assertThatCheckPortClashRejectsAsExpected() throws Exception { |
||||||
|
// use WS port for HTTP
|
||||||
|
final int port = 8546; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port), "--rpc-ws-enabled"); |
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains( |
||||||
|
"Port number '8546' has been specified multiple times. Please review the supplied configuration."); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void assertThatCheckPortClashAcceptsAsExpected() throws Exception { |
||||||
|
// use WS port for HTTP
|
||||||
|
final int port = 8546; |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port)); |
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); |
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() { |
||||||
|
parseCommand("--rpc-http-enabled", "--rpc-http-authentication-enabled"); |
||||||
|
|
||||||
|
verifyNoInteractions(mockRunnerBuilder); |
||||||
|
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||||
|
assertThat(commandErrorOutput.toString(UTF_8)) |
||||||
|
.contains( |
||||||
|
"Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpAuthenticationAlgorithIsConfigured() { |
||||||
|
parseCommand("--rpc-http-authentication-jwt-algorithm", "ES256"); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) |
||||||
|
.isEqualTo(JwtAlgorithm.ES256); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void httpAuthenticationPublicKeyIsConfigured() throws IOException { |
||||||
|
final Path publicKey = Files.createTempFile("public_key", ""); |
||||||
|
parseCommand("--rpc-http-authentication-jwt-public-key-file", publicKey.toString()); |
||||||
|
|
||||||
|
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); |
||||||
|
verify(mockRunnerBuilder).build(); |
||||||
|
|
||||||
|
assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) |
||||||
|
.isEqualTo(publicKey.toString()); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue