From d3332788ce660a1a0bd0f59ec1ed477e1855e0ad Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 8 Nov 2019 15:40:37 +1000 Subject: [PATCH 01/38] allow verification of externally signed jwt Signed-off-by: Jason Frame --- .../BesuNodeConfigurationBuilder.java | 2 +- .../org/hyperledger/besu/cli/BesuCommand.java | 26 ++++- .../besu/cli/DefaultCommandValues.java | 3 + .../besu/cli/StandaloneCommand.java | 16 +++ .../hyperledger/besu/cli/BesuCommandTest.java | 50 +++++++++ .../src/test/resources/everything_config.toml | 2 + .../api/jsonrpc/JsonRpcConfiguration.java | 59 ++++++---- .../authentication/AuthenticationService.java | 102 ++++++++++-------- .../websocket/WebSocketConfiguration.java | 92 ++++++++++------ .../websocket/WebSocketServiceLoginTest.java | 2 +- .../websocket/WebSocketServiceTest.java | 2 +- 11 files changed, 256 insertions(+), 100 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java index b9b5f3fd947..2a4bdaab699 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java @@ -130,7 +130,7 @@ public BesuNodeConfigurationBuilder webSocketEnabled() { final WebSocketConfiguration config = WebSocketConfiguration.createDefault(); config.setEnabled(true); config.setPort(0); - config.setHostsWhitelist(Collections.singleton("*")); + config.setHostsWhitelist(Collections.singletonList("*")); this.webSocketConfiguration = config; return this; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 50eca10ca02..603dc91c0c5 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1130,7 +1130,8 @@ private JsonRpcConfiguration jsonRpcConfiguration() { "--rpc-http-host", "--rpc-http-port", "--rpc-http-authentication-enabled", - "--rpc-http-authentication-credentials-file")); + "--rpc-http-authentication-credentials-file", + "--rpc-http-authentication-public-key-file")); if (isRpcHttpAuthenticationEnabled && rpcHttpAuthenticationCredentialsFile() == null) { throw new ParameterException( @@ -1147,6 +1148,7 @@ private JsonRpcConfiguration jsonRpcConfiguration() { jsonRpcConfiguration.setHostsWhitelist(hostsWhitelist); jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled); jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile()); + jsonRpcConfiguration.setAuthenticationPublicKeyFile(rpcHttpAuthenticationPublicKeyFile()); return jsonRpcConfiguration; } @@ -1163,7 +1165,8 @@ private WebSocketConfiguration webSocketConfiguration() { "--rpc-ws-host", "--rpc-ws-port", "--rpc-ws-authentication-enabled", - "--rpc-ws-authentication-credentials-file")); + "--rpc-ws-authentication-credentials-file", + "--rpc-ws-authentication-public-key-file")); if (isRpcWsAuthenticationEnabled && rpcWsAuthenticationCredentialsFile() == null) { throw new ParameterException( @@ -1179,6 +1182,7 @@ private WebSocketConfiguration webSocketConfiguration() { webSocketConfiguration.setAuthenticationEnabled(isRpcWsAuthenticationEnabled); webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile()); webSocketConfiguration.setHostsWhitelist(hostsWhitelist); + webSocketConfiguration.setAuthenticationPublicKeyFile(rpcWsAuthenticationPublicKeyFile()); return webSocketConfiguration; } @@ -1655,6 +1659,24 @@ private String rpcWsAuthenticationCredentialsFile() { return filename; } + private File rpcHttpAuthenticationPublicKeyFile() { + if (isDocker) { + final File keyFile = new File(DOCKER_RPC_HTTP_AUTHENTICATION_PUBLIC_KEY_FILE_LOCATION); + return keyFile.exists() ? keyFile : null; + } else { + return standaloneCommands.rpcHttpAuthenticationPublicKeyFile; + } + } + + private File rpcWsAuthenticationPublicKeyFile() { + if (isDocker) { + final File keyFile = new File(DOCKER_RPC_WS_AUTHENTICATION_PUBLIC_KEY_FILE_LOCATION); + return keyFile.exists() ? keyFile : null; + } else { + return standaloneCommands.rpcWsAuthenticationPublicKeyFile; + } + } + private String nodePermissionsConfigFile() { return permissionsConfigFile(standaloneCommands.nodePermissionsConfigFile); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java index f7c8f73f901..cbe89b9e425 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java @@ -57,6 +57,9 @@ public interface DefaultCommandValues { "/etc/besu/rpc_http_auth_config.toml"; String DOCKER_RPC_WS_AUTHENTICATION_CREDENTIALS_FILE_LOCATION = "/etc/besu/rpc_ws_auth_config.toml"; + String DOCKER_RPC_HTTP_AUTHENTICATION_PUBLIC_KEY_FILE_LOCATION = + "/etc/besu/rpc_http_auth_public_key"; + String DOCKER_RPC_WS_AUTHENTICATION_PUBLIC_KEY_FILE_LOCATION = "/etc/besu/rpc_ws_auth_public_key"; String DOCKER_PRIVACY_PUBLIC_KEY_FILE = "/etc/besu/privacy_public_key"; String DOCKER_PERMISSIONS_CONFIG_FILE_LOCATION = "/etc/besu/permissions_config.toml"; String PERMISSIONING_CONFIG_LOCATION = "permissions_config.toml"; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 18ade6d464d..75aa3fef812 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -65,6 +65,14 @@ class StandaloneCommand implements DefaultCommandValues { arity = "1") String rpcHttpAuthenticationCredentialsFile = null; + @CommandLine.Option( + names = {"--rpc-http-authentication-public-key-file"}, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = + "External JSON-RPC HTTP authentication public key use for validating JWT issuer.", + arity = "1") + final File rpcHttpAuthenticationPublicKeyFile = null; + @CommandLine.Option( names = {"--rpc-ws-authentication-credentials-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, @@ -73,6 +81,14 @@ class StandaloneCommand implements DefaultCommandValues { arity = "1") String rpcWsAuthenticationCredentialsFile = null; + @CommandLine.Option( + names = {"--rpc-ws-authentication-public-key-file"}, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = + "Storage file for JSON-RPC WebSocket authentication credentials (default: ${DEFAULT-VALUE})", + arity = "1") + final File rpcWsAuthenticationPublicKeyFile = null; + @CommandLine.Option( names = {"--privacy-public-key-file"}, description = "The enclave's public key file") diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index bbc0f75fbaf..b94fa70a565 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -2697,6 +2697,32 @@ public void rpcWsAuthCredentialsFileOptionDisabledUnderDocker() { assertThat(commandOutput.toString()).isEmpty(); } + @Test + public void rpcHttpAuthPublicKeyFileOptionDisabledUnderDocker() { + System.setProperty("besu.docker", "true"); + + assumeFalse(isFullInstantiation()); + + final Path path = Paths.get("."); + parseCommand("--rpc-http-authentication-public-key-file", path.toString()); + assertThat(commandErrorOutput.toString()) + .startsWith("Unknown options: --rpc-http-authentication-public-key-file, ."); + assertThat(commandOutput.toString()).isEmpty(); + } + + @Test + public void rpcWsAuthPublicKeyFileOptionDisabledUnderDocker() { + System.setProperty("besu.docker", "true"); + + assumeFalse(isFullInstantiation()); + + final Path path = Paths.get("."); + parseCommand("--rpc-ws-authentication-public-key-file", path.toString()); + assertThat(commandErrorOutput.toString()) + .startsWith("Unknown options: --rpc-ws-authentication-public-key-file, ."); + assertThat(commandOutput.toString()).isEmpty(); + } + @Test public void permissionsConfigFileOptionDisabledUnderDocker() { System.setProperty("besu.docker", "true"); @@ -2921,4 +2947,28 @@ public void requiredBlocksMulpleBlocksTwoArgs() { assertThat(requiredBlocksArg.getValue()) .containsEntry(block2, Hash.fromHexStringLenient(hash2)); } + + @Test + public void httpAuthenticationPublicKeyIsConfigured() throws IOException { + final Path publicKey = Files.createTempFile("public_key", ""); + parseCommand("--rpc-http-authentication-public-key-file", publicKey.toString()); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) + .isEqualTo(publicKey.toString()); + } + + @Test + public void wsAuthenticationPublicKeyIsConfigured() throws IOException { + final Path publicKey = Files.createTempFile("public_key", ""); + parseCommand("--rpc-ws-authentication-public-key-file", publicKey.toString()); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) + .isEqualTo(publicKey.toString()); + } } diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index b480d4f1253..3055ef85723 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -50,6 +50,7 @@ rpc-http-apis=["DEBUG","ETH"] rpc-http-cors-origins=["none"] rpc-http-authentication-enabled=false rpc-http-authentication-credentials-file="none" +rpc-http-authentication-public-key-file="none" # GRAPHQL HTTP graphql-http-enabled=false @@ -65,6 +66,7 @@ rpc-ws-host="9.10.11.12" rpc-ws-port=9101 rpc-ws-authentication-enabled=false rpc-ws-authentication-credentials-file="none" +rpc-ws-authentication-public-key-file="none" # Prometheus Metrics Endpoint metrics-enabled=false diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcConfiguration.java index 7f15c6b8037..4b1fd0c1fdb 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcConfiguration.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -35,6 +36,7 @@ public class JsonRpcConfiguration { private List hostsWhitelist = Arrays.asList("localhost", "127.0.0.1"); private boolean authenticationEnabled = false; private String authenticationCredentialsFile; + private File authenticationPublicKeyFile; public static JsonRpcConfiguration createDefault() { final JsonRpcConfiguration config = new JsonRpcConfiguration(); @@ -102,6 +104,30 @@ public void setHostsWhitelist(final List hostsWhitelist) { this.hostsWhitelist = hostsWhitelist; } + public boolean isAuthenticationEnabled() { + return authenticationEnabled; + } + + public void setAuthenticationEnabled(final boolean authenticationEnabled) { + this.authenticationEnabled = authenticationEnabled; + } + + public void setAuthenticationCredentialsFile(final String authenticationCredentialsFile) { + this.authenticationCredentialsFile = authenticationCredentialsFile; + } + + public String getAuthenticationCredentialsFile() { + return authenticationCredentialsFile; + } + + public File getAuthenticationPublicKeyFile() { + return authenticationPublicKeyFile; + } + + public void setAuthenticationPublicKeyFile(final File authenticationPublicKeyFile) { + this.authenticationPublicKeyFile = authenticationPublicKeyFile; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -113,6 +139,7 @@ public String toString() { .add("rpcApis", rpcApis) .add("authenticationEnabled", authenticationEnabled) .add("authenticationCredentialsFile", authenticationCredentialsFile) + .add("authenticationPublicKeyFile", authenticationPublicKeyFile) .toString(); } @@ -127,30 +154,26 @@ public boolean equals(final Object o) { final JsonRpcConfiguration that = (JsonRpcConfiguration) o; return enabled == that.enabled && port == that.port + && authenticationEnabled == that.authenticationEnabled && Objects.equals(host, that.host) && Objects.equals(corsAllowedDomains, that.corsAllowedDomains) + && Objects.equals(rpcApis, that.rpcApis) && Objects.equals(hostsWhitelist, that.hostsWhitelist) - && Objects.equals(rpcApis, that.rpcApis); + && Objects.equals(authenticationCredentialsFile, that.authenticationCredentialsFile) + && Objects.equals(authenticationPublicKeyFile, that.authenticationPublicKeyFile); } @Override public int hashCode() { - return Objects.hash(enabled, port, host, corsAllowedDomains, hostsWhitelist, rpcApis); - } - - public boolean isAuthenticationEnabled() { - return authenticationEnabled; - } - - public void setAuthenticationEnabled(final boolean authenticationEnabled) { - this.authenticationEnabled = authenticationEnabled; - } - - public void setAuthenticationCredentialsFile(final String authenticationCredentialsFile) { - this.authenticationCredentialsFile = authenticationCredentialsFile; - } - - public String getAuthenticationCredentialsFile() { - return authenticationCredentialsFile; + return Objects.hash( + enabled, + port, + host, + corsAllowedDomains, + rpcApis, + hostsWhitelist, + authenticationEnabled, + authenticationCredentialsFile, + authenticationPublicKeyFile); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 913f3f7a7a6..f113444d2d9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -17,6 +17,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -36,9 +39,12 @@ import io.vertx.ext.auth.jwt.JWTAuthOptions; import io.vertx.ext.jwt.JWTOptions; import io.vertx.ext.web.RoutingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** Provides authentication handlers for use in the http and websocket services */ public class AuthenticationService { + private static final Logger LOG = LogManager.getLogger(); private final JWTAuth jwtAuthProvider; @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; @@ -65,25 +71,24 @@ private AuthenticationService( */ public static Optional create( final Vertx vertx, final JsonRpcConfiguration config) { - final Optional jwtAuthOptions = - makeJwtAuthOptions( - config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); - if (!jwtAuthOptions.isPresent()) { + if (!config.isAuthenticationEnabled() || config.getAuthenticationCredentialsFile() == null) { return Optional.empty(); } - + final Optional externalJwtPublicKey = + config.getAuthenticationPublicKeyFile() == null + ? Optional.empty() + : readPublicKey(config.getAuthenticationPublicKeyFile()); + final JWTAuthOptions jwtAuthOptions = makeJwtAuthOptions(externalJwtPublicKey); final Optional credentialAuthProvider = makeCredentialAuthProvider( vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); - if (!credentialAuthProvider.isPresent()) { + if (credentialAuthProvider.isEmpty()) { return Optional.empty(); } return Optional.of( new AuthenticationService( - jwtAuthOptions.map(o -> JWTAuth.create(vertx, o)).get(), - jwtAuthOptions.get(), - credentialAuthProvider.get())); + JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider.get())); } /** @@ -98,57 +103,68 @@ public static Optional create( */ public static Optional create( final Vertx vertx, final WebSocketConfiguration config) { - final Optional jwtAuthOptions = - makeJwtAuthOptions( - config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); - if (!jwtAuthOptions.isPresent()) { + if (!config.isAuthenticationEnabled() || config.getAuthenticationCredentialsFile() == null) { return Optional.empty(); } + final Optional externalJwtPublicKey = + config.getAuthenticationPublicKeyFile() == null + ? Optional.empty() + : readPublicKey(config.getAuthenticationPublicKeyFile()); + + final JWTAuthOptions jwtAuthOptions = makeJwtAuthOptions(externalJwtPublicKey); final Optional credentialAuthProvider = makeCredentialAuthProvider( vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); - if (!credentialAuthProvider.isPresent()) { + if (credentialAuthProvider.isEmpty()) { return Optional.empty(); } return Optional.of( new AuthenticationService( - jwtAuthOptions.map(o -> JWTAuth.create(vertx, o)).get(), - jwtAuthOptions.get(), - credentialAuthProvider.get())); + JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider.get())); } - private static Optional makeJwtAuthOptions( - final boolean authenticationEnabled, @Nullable final String authenticationCredentialsFile) { - if (authenticationEnabled && authenticationCredentialsFile != null) { - final KeyPairGenerator keyGenerator; - try { - keyGenerator = KeyPairGenerator.getInstance("RSA"); - keyGenerator.initialize(1024); - } catch (final NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - final KeyPair keypair = keyGenerator.generateKeyPair(); - - final JWTAuthOptions jwtAuthOptions = - new JWTAuthOptions() - .setPermissionsClaimKey("permissions") - .addPubSecKey( - new PubSecKeyOptions() - .setAlgorithm("RS256") - .setPublicKey( - Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) - .setSecretKey( - Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); - - return Optional.of(jwtAuthOptions); - } else { + private static Optional readPublicKey(final File authenticationPublicKeyFile) { + try { + return Optional.of(Files.readString(authenticationPublicKeyFile.toPath())); + } catch (IOException e) { + LOG.error("Authentication RPC public key could not be read", e); return Optional.empty(); } } + private static JWTAuthOptions makeJwtAuthOptions(final Optional publicKey) { + if (publicKey.isEmpty()) { + final KeyPair keypair = generateJwtKeyPair(); + return new JWTAuthOptions() + .setPermissionsClaimKey("permissions") + .addPubSecKey( + new PubSecKeyOptions() + .setAlgorithm("RS256") + .setPublicKey( + Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) + .setSecretKey( + Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); + } else { + return new JWTAuthOptions() + .setPermissionsClaimKey("permissions") + .addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setPublicKey(publicKey.get())); + } + } + + private static KeyPair generateJwtKeyPair() { + final KeyPairGenerator keyGenerator; + try { + keyGenerator = KeyPairGenerator.getInstance("RSA"); + keyGenerator.initialize(1024); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + return keyGenerator.generateKeyPair(); + } + private static Optional makeCredentialAuthProvider( final Vertx vertx, final boolean authenticationEnabled, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketConfiguration.java index 3ff98c99b09..6bfdd4b3da0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketConfiguration.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; +import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -37,7 +38,8 @@ public class WebSocketConfiguration { private List rpcApis; private boolean authenticationEnabled = false; private String authenticationCredentialsFile; - private Collection hostsWhitelist = Collections.singletonList("localhost"); + private List hostsWhitelist = Arrays.asList("localhost", "127.0.0.1"); + private File authenticationPublicKeyFile; public static WebSocketConfiguration createDefault() { final WebSocketConfiguration config = new WebSocketConfiguration(); @@ -82,38 +84,6 @@ public void setRpcApis(final List rpcApis) { this.rpcApis = rpcApis; } - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("enabled", enabled) - .add("port", port) - .add("host", host) - .add("rpcApis", rpcApis) - .add("authenticationEnabled", authenticationEnabled) - .add("authenticationCredentialsFile", authenticationCredentialsFile) - .toString(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final WebSocketConfiguration that = (WebSocketConfiguration) o; - return enabled == that.enabled - && port == that.port - && Objects.equals(host, that.host) - && Objects.equals(rpcApis, that.rpcApis); - } - - @Override - public int hashCode() { - return Objects.hash(enabled, port, host, rpcApis); - } - public boolean isAuthenticationEnabled() { return authenticationEnabled; } @@ -130,11 +100,65 @@ public String getAuthenticationCredentialsFile() { return authenticationCredentialsFile; } - public void setHostsWhitelist(final Collection hostsWhitelist) { + public void setHostsWhitelist(final List hostsWhitelist) { this.hostsWhitelist = hostsWhitelist; } public Collection getHostsWhitelist() { return Collections.unmodifiableCollection(this.hostsWhitelist); } + + public File getAuthenticationPublicKeyFile() { + return authenticationPublicKeyFile; + } + + public void setAuthenticationPublicKeyFile(final File authenticationPublicKeyFile) { + this.authenticationPublicKeyFile = authenticationPublicKeyFile; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WebSocketConfiguration that = (WebSocketConfiguration) o; + return enabled == that.enabled + && port == that.port + && authenticationEnabled == that.authenticationEnabled + && Objects.equals(host, that.host) + && Objects.equals(rpcApis, that.rpcApis) + && Objects.equals(authenticationCredentialsFile, that.authenticationCredentialsFile) + && Objects.equals(hostsWhitelist, that.hostsWhitelist) + && Objects.equals(authenticationPublicKeyFile, that.authenticationPublicKeyFile); + } + + @Override + public int hashCode() { + return Objects.hash( + enabled, + port, + host, + rpcApis, + authenticationEnabled, + authenticationCredentialsFile, + hostsWhitelist, + authenticationPublicKeyFile); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("enabled", enabled) + .add("port", port) + .add("host", host) + .add("rpcApis", rpcApis) + .add("authenticationEnabled", authenticationEnabled) + .add("authenticationCredentialsFile", authenticationCredentialsFile) + .add("hostsWhitelist", hostsWhitelist) + .add("authenticationPublicKeyFile", authenticationPublicKeyFile) + .toString(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java index 4d98cbec074..03ad8b58d62 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java @@ -75,7 +75,7 @@ public void before() throws URISyntaxException { websocketConfiguration.setPort(0); websocketConfiguration.setAuthenticationEnabled(true); websocketConfiguration.setAuthenticationCredentialsFile(authTomlPath); - websocketConfiguration.setHostsWhitelist(Collections.singleton("*")); + websocketConfiguration.setHostsWhitelist(Collections.singletonList("*")); final Map websocketMethods = new WebSocketMethodsFactory( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceTest.java index 8f62a42457f..c54c2f59e98 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceTest.java @@ -60,7 +60,7 @@ public void before() { websocketConfiguration = WebSocketConfiguration.createDefault(); websocketConfiguration.setPort(0); - websocketConfiguration.setHostsWhitelist(Collections.singleton("*")); + websocketConfiguration.setHostsWhitelist(Collections.singletonList("*")); final Map websocketMethods = new WebSocketMethodsFactory( From 943f0d165c803ca841edb228ec6d8bae53277e5e Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 8 Nov 2019 16:35:38 +1000 Subject: [PATCH 02/38] move jwt configuration out of the auth service so it can be tested Signed-off-by: Jason Frame --- .../authentication/AuthenticationService.java | 97 ++++--------------- .../authentication/JWTAuthOptionsFactory.java | 65 +++++++++++++ 2 files changed, 86 insertions(+), 76 deletions(-) create mode 100644 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index f113444d2d9..ba0fddef3d3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -18,12 +18,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; import java.util.Optional; import javax.annotation.Nullable; @@ -33,18 +27,14 @@ import io.vertx.core.http.HttpServerResponse; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.AuthProvider; -import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.User; import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuthOptions; import io.vertx.ext.jwt.JWTOptions; import io.vertx.ext.web.RoutingContext; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** Provides authentication handlers for use in the http and websocket services */ public class AuthenticationService { - private static final Logger LOG = LogManager.getLogger(); private final JWTAuth jwtAuthProvider; @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; @@ -71,24 +61,11 @@ private AuthenticationService( */ public static Optional create( final Vertx vertx, final JsonRpcConfiguration config) { - if (!config.isAuthenticationEnabled() || config.getAuthenticationCredentialsFile() == null) { - return Optional.empty(); - } - final Optional externalJwtPublicKey = - config.getAuthenticationPublicKeyFile() == null - ? Optional.empty() - : readPublicKey(config.getAuthenticationPublicKeyFile()); - final JWTAuthOptions jwtAuthOptions = makeJwtAuthOptions(externalJwtPublicKey); - final Optional credentialAuthProvider = - makeCredentialAuthProvider( - vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); - if (credentialAuthProvider.isEmpty()) { - return Optional.empty(); - } - - return Optional.of( - new AuthenticationService( - JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider.get())); + return create( + vertx, + config.isAuthenticationEnabled(), + config.getAuthenticationCredentialsFile(), + config.getAuthenticationPublicKeyFile()); } /** @@ -103,19 +80,27 @@ public static Optional create( */ public static Optional create( final Vertx vertx, final WebSocketConfiguration config) { - if (!config.isAuthenticationEnabled() || config.getAuthenticationCredentialsFile() == null) { + return create( + vertx, + config.isAuthenticationEnabled(), + config.getAuthenticationCredentialsFile(), + config.getAuthenticationPublicKeyFile()); + } + + private static Optional create( + final Vertx vertx, + final boolean authenticationEnabled, + final String authenticationCredentialsFile, + final File authenticationPublicKeyFile) { + if (!authenticationEnabled || authenticationCredentialsFile == null) { return Optional.empty(); } - final Optional externalJwtPublicKey = - config.getAuthenticationPublicKeyFile() == null - ? Optional.empty() - : readPublicKey(config.getAuthenticationPublicKeyFile()); + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final JWTAuthOptions jwtAuthOptions = jwtAuthOptionsFactory.create(authenticationPublicKeyFile); - final JWTAuthOptions jwtAuthOptions = makeJwtAuthOptions(externalJwtPublicKey); final Optional credentialAuthProvider = - makeCredentialAuthProvider( - vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile()); + makeCredentialAuthProvider(vertx, authenticationEnabled, authenticationCredentialsFile); if (credentialAuthProvider.isEmpty()) { return Optional.empty(); } @@ -125,46 +110,6 @@ public static Optional create( JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider.get())); } - private static Optional readPublicKey(final File authenticationPublicKeyFile) { - try { - return Optional.of(Files.readString(authenticationPublicKeyFile.toPath())); - } catch (IOException e) { - LOG.error("Authentication RPC public key could not be read", e); - return Optional.empty(); - } - } - - private static JWTAuthOptions makeJwtAuthOptions(final Optional publicKey) { - if (publicKey.isEmpty()) { - final KeyPair keypair = generateJwtKeyPair(); - return new JWTAuthOptions() - .setPermissionsClaimKey("permissions") - .addPubSecKey( - new PubSecKeyOptions() - .setAlgorithm("RS256") - .setPublicKey( - Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) - .setSecretKey( - Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); - } else { - return new JWTAuthOptions() - .setPermissionsClaimKey("permissions") - .addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setPublicKey(publicKey.get())); - } - } - - private static KeyPair generateJwtKeyPair() { - final KeyPairGenerator keyGenerator; - try { - keyGenerator = KeyPairGenerator.getInstance("RSA"); - keyGenerator.initialize(1024); - } catch (final NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - return keyGenerator.generateKeyPair(); - } - private static Optional makeCredentialAuthProvider( final Vertx vertx, final boolean authenticationEnabled, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java new file mode 100644 index 00000000000..89b85c4242d --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -0,0 +1,65 @@ +package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Optional; + +import io.vertx.ext.auth.PubSecKeyOptions; +import io.vertx.ext.auth.jwt.JWTAuthOptions; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class JWTAuthOptionsFactory { + private static final Logger LOG = LogManager.getLogger(); + + public JWTAuthOptions create(final File externalPublicKeyFile) { + final Optional externalJwtPublicKey = + externalPublicKeyFile == null ? Optional.empty() : readPublicKey(externalPublicKeyFile); + return makeJwtAuthOptions(externalJwtPublicKey); + } + + private Optional readPublicKey(final File authenticationPublicKeyFile) { + try { + return Optional.of(Files.readString(authenticationPublicKeyFile.toPath())); + } catch (IOException e) { + LOG.error("Authentication RPC public key could not be read", e); + return Optional.empty(); + } + } + + private JWTAuthOptions makeJwtAuthOptions(final Optional publicKey) { + if (publicKey.isEmpty()) { + final KeyPair keypair = generateJwtKeyPair(); + return new JWTAuthOptions() + .setPermissionsClaimKey("permissions") + .addPubSecKey( + new PubSecKeyOptions() + .setAlgorithm("RS256") + .setPublicKey( + Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) + .setSecretKey( + Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); + } else { + return new JWTAuthOptions() + .setPermissionsClaimKey("permissions") + .addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setPublicKey(publicKey.get())); + } + } + + private KeyPair generateJwtKeyPair() { + final KeyPairGenerator keyGenerator; + try { + keyGenerator = KeyPairGenerator.getInstance("RSA"); + keyGenerator.initialize(1024); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + return keyGenerator.generateKeyPair(); + } +} From 454368153d07aae64e312e42a5aa7777a2f3e2a6 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Mon, 11 Nov 2019 11:32:24 +1000 Subject: [PATCH 03/38] jwt auth options ut Signed-off-by: Jason Frame --- .../authentication/AuthenticationService.java | 7 +- .../authentication/JWTAuthOptionsFactory.java | 55 ++++++-------- .../JWTAuthOptionsFactoryTest.java | 73 +++++++++++++++++++ 3 files changed, 102 insertions(+), 33 deletions(-) create mode 100644 ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index ba0fddef3d3..18716e5f51a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -39,6 +39,7 @@ public class AuthenticationService { private final JWTAuth jwtAuthProvider; @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; private final AuthProvider credentialAuthProvider; + private static final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); private AuthenticationService( final JWTAuth jwtAuthProvider, @@ -96,8 +97,10 @@ private static Optional create( return Optional.empty(); } - final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); - final JWTAuthOptions jwtAuthOptions = jwtAuthOptionsFactory.create(authenticationPublicKeyFile); + final JWTAuthOptions jwtAuthOptions = + authenticationPublicKeyFile == null + ? jwtAuthOptionsFactory.createWithGeneratedKeyPair() + : jwtAuthOptionsFactory.createForExternalPublicKey(authenticationPublicKeyFile); final Optional credentialAuthProvider = makeCredentialAuthProvider(vertx, authenticationEnabled, authenticationCredentialsFile); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 89b85c4242d..8babcbff482 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -7,47 +7,40 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.Base64; -import java.util.Optional; import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.jwt.JWTAuthOptions; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; public class JWTAuthOptionsFactory { - private static final Logger LOG = LogManager.getLogger(); - public JWTAuthOptions create(final File externalPublicKeyFile) { - final Optional externalJwtPublicKey = - externalPublicKeyFile == null ? Optional.empty() : readPublicKey(externalPublicKeyFile); - return makeJwtAuthOptions(externalJwtPublicKey); + private static final String ALGORITHM = "RS256"; + private static final String PERMISSIONS = "permissions"; + + public JWTAuthOptions createForExternalPublicKey(final File externalPublicKeyFile) { + final String externalJwtPublicKey = readPublicKey(externalPublicKeyFile); + return new JWTAuthOptions() + .setPermissionsClaimKey(PERMISSIONS) + .addPubSecKey( + new PubSecKeyOptions().setAlgorithm(ALGORITHM).setPublicKey(externalJwtPublicKey)); } - private Optional readPublicKey(final File authenticationPublicKeyFile) { - try { - return Optional.of(Files.readString(authenticationPublicKeyFile.toPath())); - } catch (IOException e) { - LOG.error("Authentication RPC public key could not be read", e); - return Optional.empty(); - } + public JWTAuthOptions createWithGeneratedKeyPair() { + final KeyPair keypair = generateJwtKeyPair(); + return new JWTAuthOptions() + .setPermissionsClaimKey(PERMISSIONS) + .addPubSecKey( + new PubSecKeyOptions() + .setAlgorithm(ALGORITHM) + .setPublicKey(Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) + .setSecretKey( + Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); } - private JWTAuthOptions makeJwtAuthOptions(final Optional publicKey) { - if (publicKey.isEmpty()) { - final KeyPair keypair = generateJwtKeyPair(); - return new JWTAuthOptions() - .setPermissionsClaimKey("permissions") - .addPubSecKey( - new PubSecKeyOptions() - .setAlgorithm("RS256") - .setPublicKey( - Base64.getEncoder().encodeToString(keypair.getPublic().getEncoded())) - .setSecretKey( - Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); - } else { - return new JWTAuthOptions() - .setPermissionsClaimKey("permissions") - .addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setPublicKey(publicKey.get())); + private String readPublicKey(final File authenticationPublicKeyFile) { + try { + return Files.readString(authenticationPublicKeyFile.toPath()); + } catch (IOException e) { + throw new IllegalStateException("Authentication RPC public key could not be read", e); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java new file mode 100644 index 00000000000..85d5e368f18 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java @@ -0,0 +1,73 @@ +package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import io.vertx.ext.auth.PubSecKeyOptions; +import io.vertx.ext.auth.jwt.JWTAuthOptions; +import org.junit.Test; + +public class JWTAuthOptionsFactoryTest { + + private static final String JWT_PUBLIC_KEY = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6tMhjogMulRbYby7bCL" + + "rFhukDnxvm4XR3KSXLKdLLQHHyouMOQaLac9M+/Z1KkIpqfZPjLfW2/yUg2IKx4T" + + "dvFVzbVq17X6dq49ZS8jJtb8l2+Vius4d3LnpvxCOematRG9Acn+2qLwC+sK7RPY" + + "OxEqKPU5LNBH1C0FfviazY5jkixBFICzIq/SyyRnGX+iIONnNsu0TlhWVLSlZbg5" + + "NYf4cAzu/1d5MgspyZwnRo468gqaak3wQzkmk69Z25L1N7TXZvk2b7rT7/ssFnt+" + + "//fKVpD6qkQ3OopD+7gOziAYUxChw6RUWekV+uRgNADQhaqV6wDdogBz77wTJedV" + + "YwIDAQAB"; + + @Test + public void createsOptionsWithGeneratedKeyPair() { + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final JWTAuthOptions jwtAuthOptions = jwtAuthOptionsFactory.createWithGeneratedKeyPair(); + + assertThat(jwtAuthOptions.getPubSecKeys()).isNotNull(); + assertThat(jwtAuthOptions.getPubSecKeys()).hasSize(1); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getAlgorithm()).isEqualTo("RS256"); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getPublicKey()).isNotEmpty(); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getSecretKey()).isNotEmpty(); + } + + @Test + public void createsOptionsWithGeneratedKeyPairThatIsDifferentEachTime() { + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final JWTAuthOptions jwtAuthOptions1 = jwtAuthOptionsFactory.createWithGeneratedKeyPair(); + final JWTAuthOptions jwtAuthOptions2 = jwtAuthOptionsFactory.createWithGeneratedKeyPair(); + + final PubSecKeyOptions pubSecKeyOptions1 = jwtAuthOptions1.getPubSecKeys().get(0); + final PubSecKeyOptions pubSecKeyOptions2 = jwtAuthOptions2.getPubSecKeys().get(0); + assertThat(pubSecKeyOptions1.getPublicKey()).isNotEqualTo(pubSecKeyOptions2.getPublicKey()); + assertThat(pubSecKeyOptions1.getSecretKey()).isNotEqualTo(pubSecKeyOptions2.getSecretKey()); + } + + @Test + public void createsOptionsUsingPublicKeyFile() throws IOException { + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final Path enclavePublicKey = Files.createTempFile("enclave", "pub"); + Files.writeString(enclavePublicKey, JWT_PUBLIC_KEY); + + final JWTAuthOptions jwtAuthOptions = + jwtAuthOptionsFactory.createForExternalPublicKey(enclavePublicKey.toFile()); + assertThat(jwtAuthOptions.getPubSecKeys()).hasSize(1); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getAlgorithm()).isEqualTo("RS256"); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getSecretKey()).isNull(); + assertThat(jwtAuthOptions.getPubSecKeys().get(0).getPublicKey()).isEqualTo(JWT_PUBLIC_KEY); + } + + @Test + public void failsToCreateOptionsWhenPublicKeyFileDoesNotExist() { + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final File enclavePublicKeyFile = new File("doesNotExist"); + + assertThatThrownBy(() -> jwtAuthOptionsFactory.createForExternalPublicKey(enclavePublicKeyFile)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Authentication RPC public key could not be read"); + } +} From 778a6c5ad673336da29d55cb80a73cb2d9405505 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Mon, 11 Nov 2019 11:40:53 +1000 Subject: [PATCH 04/38] add file headers Signed-off-by: Jason Frame --- .../authentication/JWTAuthOptionsFactory.java | 14 ++++++++++++++ .../authentication/JWTAuthOptionsFactoryTest.java | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 8babcbff482..81b16ef196d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -1,3 +1,17 @@ +/* + * Copyright ConsenSys AG. + * + * 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.ethereum.api.jsonrpc.authentication; import java.io.File; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java index 85d5e368f18..fcb448ba80c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java @@ -1,3 +1,17 @@ +/* + * Copyright ConsenSys AG. + * + * 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.ethereum.api.jsonrpc.authentication; import static org.assertj.core.api.Assertions.assertThat; From 22066b6dabbfed21d71bf7c862afacfc1ebdaef3 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Mon, 11 Nov 2019 14:59:46 +1000 Subject: [PATCH 05/38] increase generated key size to 2048 as recommended in the jwt rfc when using rsa Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/JWTAuthOptionsFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 81b16ef196d..8c645b8b4e0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -62,7 +62,7 @@ private KeyPair generateJwtKeyPair() { final KeyPairGenerator keyGenerator; try { keyGenerator = KeyPairGenerator.getInstance("RSA"); - keyGenerator.initialize(1024); + keyGenerator.initialize(2048); } catch (final NoSuchAlgorithmException e) { throw new RuntimeException(e); } From a55d2ea6cf06375798554b08564486c11d10ab7b Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Mon, 11 Nov 2019 15:33:03 +1000 Subject: [PATCH 06/38] disable login if external jwt public key is used Signed-off-by: Jason Frame --- .../api/jsonrpc/JsonRpcHttpService.java | 2 +- .../authentication/AuthenticationService.java | 17 ++++++++++++++--- .../api/jsonrpc/websocket/WebSocketService.java | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java index 4886379ab40..f9013b116b0 100755 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java @@ -204,7 +204,7 @@ public CompletableFuture start() { .produces(APPLICATION_JSON) .handler(this::handleJsonRPCRequest); - if (authenticationService.isPresent()) { + if (authenticationService.map(AuthenticationService::canHandleLogin).orElse(false)) { router .route("/login") .method(HttpMethod.POST) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 18716e5f51a..1036a058176 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -39,15 +39,18 @@ public class AuthenticationService { private final JWTAuth jwtAuthProvider; @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; private final AuthProvider credentialAuthProvider; + private final boolean hasExternalPublicKey; private static final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); private AuthenticationService( final JWTAuth jwtAuthProvider, final JWTAuthOptions jwtAuthOptions, - final AuthProvider credentialAuthProvider) { + final AuthProvider credentialAuthProvider, + final boolean hasExternalPublicKey) { this.jwtAuthProvider = jwtAuthProvider; this.jwtAuthOptions = jwtAuthOptions; this.credentialAuthProvider = credentialAuthProvider; + this.hasExternalPublicKey = hasExternalPublicKey; } /** @@ -97,8 +100,9 @@ private static Optional create( return Optional.empty(); } + final boolean hasExternalPublicKey = authenticationPublicKeyFile == null; final JWTAuthOptions jwtAuthOptions = - authenticationPublicKeyFile == null + hasExternalPublicKey ? jwtAuthOptionsFactory.createWithGeneratedKeyPair() : jwtAuthOptionsFactory.createForExternalPublicKey(authenticationPublicKeyFile); @@ -110,7 +114,10 @@ private static Optional create( return Optional.of( new AuthenticationService( - JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider.get())); + JWTAuth.create(vertx, jwtAuthOptions), + jwtAuthOptions, + credentialAuthProvider.get(), + hasExternalPublicKey)); } private static Optional makeCredentialAuthProvider( @@ -192,4 +199,8 @@ public void handleLogin(final RoutingContext routingContext) { public JWTAuth getJwtAuthProvider() { return jwtAuthProvider; } + + public boolean canHandleLogin() { + return hasExternalPublicKey; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java index 463c1da2e3a..04bf81fd9d4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java @@ -153,7 +153,7 @@ private Handler httpHandler() { // Verify Host header to avoid rebind attack. router.route().handler(checkWhitelistHostHeader()); - if (authenticationService.isPresent()) { + if (authenticationService.map(AuthenticationService::canHandleLogin).orElse(false)) { router.route("/login").handler(BodyHandler.create()); router .post("/login") From 5708c080715b32eea1228b8c149299d5c17c87d8 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 10:44:03 +1000 Subject: [PATCH 07/38] handle JWT auth without credentials file Signed-off-by: Jason Frame --- .../org/hyperledger/besu/cli/BesuCommand.java | 4 +- .../api/jsonrpc/JsonRpcHttpService.java | 2 +- .../authentication/AuthenticationService.java | 41 ++++++------------- .../jsonrpc/websocket/WebSocketService.java | 2 +- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 0087b1db4ab..ab4a6c511ed 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1133,7 +1133,9 @@ private JsonRpcConfiguration jsonRpcConfiguration() { "--rpc-http-authentication-credentials-file", "--rpc-http-authentication-public-key-file")); - if (isRpcHttpAuthenticationEnabled && rpcHttpAuthenticationCredentialsFile() == null) { + if (isRpcHttpAuthenticationEnabled + && rpcHttpAuthenticationCredentialsFile() == null + && rpcHttpAuthenticationPublicKeyFile() == null) { throw new ParameterException( commandLine, "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java index f9013b116b0..4886379ab40 100755 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java @@ -204,7 +204,7 @@ public CompletableFuture start() { .produces(APPLICATION_JSON) .handler(this::handleJsonRPCRequest); - if (authenticationService.map(AuthenticationService::canHandleLogin).orElse(false)) { + if (authenticationService.isPresent()) { router .route("/login") .method(HttpMethod.POST) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 1036a058176..006a2133f98 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -21,7 +21,6 @@ import java.util.Optional; import javax.annotation.Nullable; -import com.google.common.annotations.VisibleForTesting; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServerResponse; @@ -37,20 +36,13 @@ public class AuthenticationService { private final JWTAuth jwtAuthProvider; - @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; - private final AuthProvider credentialAuthProvider; - private final boolean hasExternalPublicKey; + private final Optional credentialAuthProvider; private static final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); private AuthenticationService( - final JWTAuth jwtAuthProvider, - final JWTAuthOptions jwtAuthOptions, - final AuthProvider credentialAuthProvider, - final boolean hasExternalPublicKey) { + final JWTAuth jwtAuthProvider, final Optional credentialAuthProvider) { this.jwtAuthProvider = jwtAuthProvider; - this.jwtAuthOptions = jwtAuthOptions; this.credentialAuthProvider = credentialAuthProvider; - this.hasExternalPublicKey = hasExternalPublicKey; } /** @@ -96,28 +88,16 @@ private static Optional create( final boolean authenticationEnabled, final String authenticationCredentialsFile, final File authenticationPublicKeyFile) { - if (!authenticationEnabled || authenticationCredentialsFile == null) { - return Optional.empty(); - } - - final boolean hasExternalPublicKey = authenticationPublicKeyFile == null; final JWTAuthOptions jwtAuthOptions = - hasExternalPublicKey + authenticationPublicKeyFile == null ? jwtAuthOptionsFactory.createWithGeneratedKeyPair() : jwtAuthOptionsFactory.createForExternalPublicKey(authenticationPublicKeyFile); final Optional credentialAuthProvider = makeCredentialAuthProvider(vertx, authenticationEnabled, authenticationCredentialsFile); - if (credentialAuthProvider.isEmpty()) { - return Optional.empty(); - } return Optional.of( - new AuthenticationService( - JWTAuth.create(vertx, jwtAuthOptions), - jwtAuthOptions, - credentialAuthProvider.get(), - hasExternalPublicKey)); + new AuthenticationService(JWTAuth.create(vertx, jwtAuthOptions), credentialAuthProvider)); } private static Optional makeCredentialAuthProvider( @@ -152,6 +132,15 @@ public static void handleDisabledLogin(final RoutingContext routingContext) { * @param routingContext Routing context associated with this request */ public void handleLogin(final RoutingContext routingContext) { + if (credentialAuthProvider.isPresent()) { + login(routingContext, credentialAuthProvider.get()); + } else { + handleDisabledLogin(routingContext); + } + } + + private void login( + final RoutingContext routingContext, final AuthProvider credentialAuthProvider) { final JsonObject requestBody = routingContext.getBodyAsJson(); if (requestBody == null) { @@ -199,8 +188,4 @@ public void handleLogin(final RoutingContext routingContext) { public JWTAuth getJwtAuthProvider() { return jwtAuthProvider; } - - public boolean canHandleLogin() { - return hasExternalPublicKey; - } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java index 04bf81fd9d4..463c1da2e3a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java @@ -153,7 +153,7 @@ private Handler httpHandler() { // Verify Host header to avoid rebind attack. router.route().handler(checkWhitelistHostHeader()); - if (authenticationService.map(AuthenticationService::canHandleLogin).orElse(false)) { + if (authenticationService.isPresent()) { router.route("/login").handler(BodyHandler.create()); router .post("/login") From a55558cc9ce6e52ae8ef9d857f22f416550b636b Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 12:48:55 +1000 Subject: [PATCH 08/38] Only allow Auth service to be enabled if auth is enabled. Fixes the ATs Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/AuthenticationService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 006a2133f98..72102d81f10 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -88,6 +88,12 @@ private static Optional create( final boolean authenticationEnabled, final String authenticationCredentialsFile, final File authenticationPublicKeyFile) { + if (!authenticationEnabled + && authenticationCredentialsFile == null + && authenticationPublicKeyFile == null) { + return Optional.empty(); + } + final JWTAuthOptions jwtAuthOptions = authenticationPublicKeyFile == null ? jwtAuthOptionsFactory.createWithGeneratedKeyPair() From 9d24f8d7456d9c9b5d1362ea0c6c17c4bab4cfe2 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 13:00:25 +1000 Subject: [PATCH 09/38] http rpc JWT external public key ATs Signed-off-by: Jason Frame --- .../condition/login/ExpectLoginDisabled.java | 27 +++++++++++++ .../dsl/condition/login/LoginConditions.java | 4 ++ .../dsl/node/ProcessBesuNodeRunner.java | 4 ++ .../BesuNodeConfigurationBuilder.java | 14 +++++++ .../node/configuration/BesuNodeFactory.java | 11 ++++++ .../login/LoginDisabledTransaction.java | 39 +++++++++++++++++++ .../login/LoginUnauthorizedTransaction.java | 3 +- .../HttpServiceLoginAcceptanceTest.java | 29 ++++++++++++++ .../resources/authentication/jwt_public_key | 7 ++++ .../org/hyperledger/besu/cli/BesuCommand.java | 2 +- 10 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/ExpectLoginDisabled.java create mode 100644 acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java create mode 100644 acceptance-tests/tests/src/test/resources/authentication/jwt_public_key diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/ExpectLoginDisabled.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/ExpectLoginDisabled.java new file mode 100644 index 00000000000..6f68a38c105 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/ExpectLoginDisabled.java @@ -0,0 +1,27 @@ +/* + * Copyright ConsenSys AG. + * + * 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.tests.acceptance.dsl.condition.login; + +import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; +import org.hyperledger.besu.tests.acceptance.dsl.node.Node; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.login.LoginDisabledTransaction; + +public class ExpectLoginDisabled implements Condition { + + @Override + public void verify(final Node node) { + node.execute(new LoginDisabledTransaction()); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/LoginConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/LoginConditions.java index 09e4e831547..234d1324b93 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/LoginConditions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/login/LoginConditions.java @@ -27,6 +27,10 @@ public Condition failure(final String username, final String password) { return new ExpectLoginUnauthorized(username, password); } + public Condition disabled() { + return new ExpectLoginDisabled(); + } + public Condition awaitResponse(final String username, final String password) { return new AwaitLoginResponse<>(new LoginTransaction(username, password)); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index a9e6fb8b848..e180beddecc 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -125,6 +125,10 @@ public void startNode(final BesuNode node) { params.add("--rpc-http-authentication-credentials-file"); params.add(node.jsonRpcConfiguration().getAuthenticationCredentialsFile()); } + if (node.jsonRpcConfiguration().getAuthenticationPublicKeyFile() != null) { + params.add("--rpc-http-authentication-public-key-file"); + params.add(node.jsonRpcConfiguration().getAuthenticationPublicKeyFile().getAbsolutePath()); + } } if (node.wsRpcEnabled()) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java index 2a4bdaab699..124d127c1b2 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; +import java.io.File; import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.ArrayList; @@ -114,6 +115,19 @@ public BesuNodeConfigurationBuilder jsonRpcAuthenticationEnabled() throws URISyn return this; } + public BesuNodeConfigurationBuilder jsonRpcAuthenticationUsingPublicKeyEnabled() + throws URISyntaxException { + final File jwtPublicKey = + Paths.get(ClassLoader.getSystemResource("authentication/jwt_public_key").toURI()) + .toAbsolutePath() + .toFile(); + + this.jsonRpcConfiguration.setAuthenticationEnabled(true); + this.jsonRpcConfiguration.setAuthenticationPublicKeyFile(jwtPublicKey); + + return this; + } + public BesuNodeConfigurationBuilder webSocketConfiguration( final WebSocketConfiguration webSocketConfiguration) { this.webSocketConfiguration = webSocketConfiguration; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index d40b1140a6b..f2cf9fd804e 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -139,6 +139,17 @@ public BesuNode createArchiveNodeWithAuthentication(final String name) .build()); } + public BesuNode createArchiveNodeWithAuthenticationUsingJwtPublicKey(final String name) + throws IOException, URISyntaxException { + return create( + new BesuNodeConfigurationBuilder() + .name(name) + .jsonRpcEnabled() + .jsonRpcAuthenticationUsingPublicKeyEnabled() + .webSocketEnabled() + .build()); + } + public BesuNode createArchiveNodeWithAuthenticationOverWebSocket(final String name) throws IOException, URISyntaxException { return create( diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java new file mode 100644 index 00000000000..5ee0bd31394 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java @@ -0,0 +1,39 @@ +/* + * Copyright ConsenSys AG. + * + * 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.tests.acceptance.dsl.transaction.login; + +import static org.assertj.core.api.Fail.fail; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +import org.assertj.core.api.Assertions; + +public class LoginDisabledTransaction implements Transaction { + + @Override + public Void execute(final NodeRequests node) { + try { + String send = node.login().send("user", "password"); + Assertions.assertThat(send).isEqualTo("Authentication not enabled"); + return null; + } catch (final IOException e) { + fail("Login request failed with exception: %s", e); + return null; + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java index fd5ddf477a4..5583a222f61 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java @@ -36,7 +36,8 @@ public LoginUnauthorizedTransaction(final String username, final String password @Override public Void execute(final NodeRequests node) { try { - Assertions.assertThat(node.login().send(username, password)).isEqualTo("Unauthorized"); + String send = node.login().send(username, password); + Assertions.assertThat(send).isEqualTo("Unauthorized"); return null; } catch (final IOException e) { fail("Login request failed with exception: %s", e); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 58cd487175d..a990fdbe90c 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -29,7 +29,17 @@ public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase { private Cluster authenticatedCluster; + private Cluster authenticatedClusterWithJwtPublicKey; private BesuNode node; + private BesuNode nodeUsingJwtPublicKey; + + private static final String TOKEN = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjI" + + "sInBlcm1pc3Npb25zIjpbIm5ldDpwZWVyQ291bnQiXX0.fXi73v4UTEO3hiG0AzPaD-OjQy0rL0SY-tMNCfJMdiVde" + + "im7Erwq4sVCrFtmx0tUs-e5Z1t_K-Gx6c_95911T2Jq2VLlwKJDs0FYEGgq2G3W-PMMrT21SPLJM-r7kl9_k51Xbww" + + "D7Cku_JFaLmkhd_l8k-EmGCTCWar514HUTlH0pm4nYhDKa7SuMAqMUo8CSZRCEzSD_AeOShJTk02cPtkCqXzClK3XO" + + "gfxsO5viuklX13VT35lyG-HNNuReLX6U4nWu_irHv0r7Gl8MVFz0Ohm0bA_G1OUh5ue6y7DcYADOoYTmvfSgkKD0hl" + + "bKx3j3tp1PX6Cw_fZUjviFvwxEg"; @Before public void setUp() throws IOException, URISyntaxException { @@ -40,6 +50,11 @@ public void setUp() throws IOException, URISyntaxException { node = besu.createArchiveNodeWithAuthentication("node1"); authenticatedCluster.start(node); node.verify(login.awaitResponse("user", "badpassword")); + + authenticatedClusterWithJwtPublicKey = new Cluster(clusterConfiguration, net); + nodeUsingJwtPublicKey = besu.createArchiveNodeWithAuthenticationUsingJwtPublicKey("node2"); + authenticatedClusterWithJwtPublicKey.start(nodeUsingJwtPublicKey); + nodeUsingJwtPublicKey.verify(login.awaitResponse("user", "badpassword")); } @Test @@ -62,9 +77,23 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { node.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } + @Test + public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); + + nodeUsingJwtPublicKey.verify(net.awaitPeerCount(0)); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); + } + + @Test + public void loginShouldBeDisabledWhenUsingExternalJwtPublicKey() { + nodeUsingJwtPublicKey.verify(login.disabled()); + } + @Override public void tearDownAcceptanceTestBase() { authenticatedCluster.stop(); + authenticatedClusterWithJwtPublicKey.stop(); super.tearDownAcceptanceTestBase(); } } diff --git a/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key b/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key new file mode 100644 index 00000000000..c0c4559808e --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key @@ -0,0 +1,7 @@ +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6tMhjogMulRbYby7bCL +rFhukDnxvm4XR3KSXLKdLLQHHyouMOQaLac9M+/Z1KkIpqfZPjLfW2/yUg2IKx4T +dvFVzbVq17X6dq49ZS8jJtb8l2+Vius4d3LnpvxCOematRG9Acn+2qLwC+sK7RPY +OxEqKPU5LNBH1C0FfviazY5jkixBFICzIq/SyyRnGX+iIONnNsu0TlhWVLSlZbg5 +NYf4cAzu/1d5MgspyZwnRo468gqaak3wQzkmk69Z25L1N7TXZvk2b7rT7/ssFnt+ +//fKVpD6qkQ3OopD+7gOziAYUxChw6RUWekV+uRgNADQhaqV6wDdogBz77wTJedV +YwIDAQAB \ No newline at end of file diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index ab4a6c511ed..5659e88fd03 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1138,7 +1138,7 @@ && rpcHttpAuthenticationCredentialsFile() == null && rpcHttpAuthenticationPublicKeyFile() == null) { throw new ParameterException( commandLine, - "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file"); + "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); } final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); From 44874a0aaa1459a2c567ceba5ba654b851db4a74 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 14:41:19 +1000 Subject: [PATCH 10/38] ws rpc JWT external public key ATs Signed-off-by: Jason Frame --- .../dsl/node/ProcessBesuNodeRunner.java | 5 ++++ .../BesuNodeConfigurationBuilder.java | 13 +++++++++ .../node/configuration/BesuNodeFactory.java | 10 +++++++ .../login/LoginDisabledTransaction.java | 2 +- .../WebsocketServiceLoginAcceptanceTest.java | 29 +++++++++++++++++-- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index e180beddecc..ae6ed7510ce 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -146,6 +146,11 @@ public void startNode(final BesuNode node) { params.add("--rpc-ws-authentication-credentials-file"); params.add(node.webSocketConfiguration().getAuthenticationCredentialsFile()); } + if (node.webSocketConfiguration().getAuthenticationPublicKeyFile() != null) { + params.add("--rpc-ws-authentication-public-key-file"); + params.add( + node.webSocketConfiguration().getAuthenticationPublicKeyFile().getAbsolutePath()); + } } if (node.isMetricsEnabled()) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java index 124d127c1b2..de7d09bf3d5 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java @@ -167,6 +167,19 @@ public BesuNodeConfigurationBuilder webSocketAuthenticationEnabled() throws URIS return this; } + public BesuNodeConfigurationBuilder webSocketAuthenticationUsingPublicKeyEnabled() + throws URISyntaxException { + final File jwtPublicKey = + Paths.get(ClassLoader.getSystemResource("authentication/jwt_public_key").toURI()) + .toAbsolutePath() + .toFile(); + + this.webSocketConfiguration.setAuthenticationEnabled(true); + this.webSocketConfiguration.setAuthenticationPublicKeyFile(jwtPublicKey); + + return this; + } + public BesuNodeConfigurationBuilder permissioningConfiguration( final PermissioningConfiguration permissioningConfiguration) { this.permissioningConfiguration = Optional.of(permissioningConfiguration); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index f2cf9fd804e..61870ab9fc6 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -160,6 +160,16 @@ public BesuNode createArchiveNodeWithAuthenticationOverWebSocket(final String na .build()); } + public BesuNode createArchiveNodeWithAuthenticationUsingJwtPublicKeyOverWebSocket( + final String name) throws IOException, URISyntaxException { + return create( + new BesuNodeConfigurationBuilder() + .name(name) + .webSocketEnabled() + .webSocketAuthenticationUsingPublicKeyEnabled() + .build()); + } + public BesuNode createNodeWithP2pDisabled(final String name) throws IOException { return create( new BesuNodeConfigurationBuilder() diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java index 5ee0bd31394..05ba8384f5a 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java @@ -32,7 +32,7 @@ public Void execute(final NodeRequests node) { Assertions.assertThat(send).isEqualTo("Authentication not enabled"); return null; } catch (final IOException e) { - fail("Login request failed with exception: %s", e); + fail("Login request failed with exception: ", e); return null; } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index 96e7872138b..c7c1f04112f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -25,12 +25,24 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { private BesuNode node; + private BesuNode nodeUsingJwtPublicKey; + + private static final String TOKEN = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjI" + + "sInBlcm1pc3Npb25zIjpbIm5ldDpwZWVyQ291bnQiXX0.fXi73v4UTEO3hiG0AzPaD-OjQy0rL0SY-tMNCfJMdiVde" + + "im7Erwq4sVCrFtmx0tUs-e5Z1t_K-Gx6c_95911T2Jq2VLlwKJDs0FYEGgq2G3W-PMMrT21SPLJM-r7kl9_k51Xbww" + + "D7Cku_JFaLmkhd_l8k-EmGCTCWar514HUTlH0pm4nYhDKa7SuMAqMUo8CSZRCEzSD_AeOShJTk02cPtkCqXzClK3XO" + + "gfxsO5viuklX13VT35lyG-HNNuReLX6U4nWu_irHv0r7Gl8MVFz0Ohm0bA_G1OUh5ue6y7DcYADOoYTmvfSgkKD0hl" + + "bKx3j3tp1PX6Cw_fZUjviFvwxEg"; @Before public void setUp() throws IOException, URISyntaxException { node = besu.createArchiveNodeWithAuthenticationOverWebSocket("node1"); - cluster.start(node); + nodeUsingJwtPublicKey = + besu.createArchiveNodeWithAuthenticationUsingJwtPublicKeyOverWebSocket("node2"); + cluster.start(node, nodeUsingJwtPublicKey); node.useWebSocketsForJsonRpc(); + nodeUsingJwtPublicKey.useWebSocketsForJsonRpc(); } @Test @@ -48,7 +60,20 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { final String token = node.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); node.useAuthenticationTokenInHeaderForJsonRpc(token); - node.verify(net.awaitPeerCount(0)); + node.verify(net.awaitPeerCount(1)); node.verify(net.netVersionUnauthorizedResponse()); } + + @Test + public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); + + nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedResponse()); + } + + @Test + public void loginShouldBeDisabledWhenUsingExternalJwtPublicKey() { + nodeUsingJwtPublicKey.verify(login.disabled()); + } } From 1d770b753837c92dd5b1924cc9914be1cac6ca5f Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 14:57:10 +1000 Subject: [PATCH 11/38] refactor login ATs Signed-off-by: Jason Frame --- .../HttpServiceLoginAcceptanceTest.java | 29 ++++++++----------- .../WebsocketServiceLoginAcceptanceTest.java | 20 ++++++------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index a990fdbe90c..342864a6ab9 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -27,10 +27,8 @@ import org.junit.Test; public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase { - private Cluster authenticatedCluster; - private Cluster authenticatedClusterWithJwtPublicKey; - private BesuNode node; + private BesuNode nodeUsingAuthFile; private BesuNode nodeUsingJwtPublicKey; private static final String TOKEN = @@ -45,43 +43,41 @@ public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase { public void setUp() throws IOException, URISyntaxException { final ClusterConfiguration clusterConfiguration = new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); - authenticatedCluster = new Cluster(clusterConfiguration, net); - node = besu.createArchiveNodeWithAuthentication("node1"); - authenticatedCluster.start(node); - node.verify(login.awaitResponse("user", "badpassword")); - authenticatedClusterWithJwtPublicKey = new Cluster(clusterConfiguration, net); + nodeUsingAuthFile = besu.createArchiveNodeWithAuthentication("node1"); nodeUsingJwtPublicKey = besu.createArchiveNodeWithAuthenticationUsingJwtPublicKey("node2"); - authenticatedClusterWithJwtPublicKey.start(nodeUsingJwtPublicKey); + authenticatedCluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); + + nodeUsingAuthFile.verify(login.awaitResponse("user", "badpassword")); nodeUsingJwtPublicKey.verify(login.awaitResponse("user", "badpassword")); } @Test public void shouldFailLoginWithWrongCredentials() { - node.verify(login.failure("user", "badpassword")); + nodeUsingAuthFile.verify(login.failure("user", "badpassword")); } @Test public void shouldSucceedLoginWithCorrectCredentials() { - node.verify(login.success("user", "pegasys")); + nodeUsingAuthFile.verify(login.success("user", "pegasys")); } @Test public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { final String token = - node.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); - node.useAuthenticationTokenInHeaderForJsonRpc(token); + nodeUsingAuthFile.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); - node.verify(net.awaitPeerCount(0)); - node.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); + nodeUsingAuthFile.verify(net.awaitPeerCount(1)); + nodeUsingAuthFile.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); - nodeUsingJwtPublicKey.verify(net.awaitPeerCount(0)); + nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } @@ -93,7 +89,6 @@ public void loginShouldBeDisabledWhenUsingExternalJwtPublicKey() { @Override public void tearDownAcceptanceTestBase() { authenticatedCluster.stop(); - authenticatedClusterWithJwtPublicKey.stop(); super.tearDownAcceptanceTestBase(); } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index c7c1f04112f..dd3c44f5fa8 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -24,7 +24,7 @@ import org.junit.Test; public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { - private BesuNode node; + private BesuNode nodeUsingAuthFile; private BesuNode nodeUsingJwtPublicKey; private static final String TOKEN = @@ -37,31 +37,31 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { @Before public void setUp() throws IOException, URISyntaxException { - node = besu.createArchiveNodeWithAuthenticationOverWebSocket("node1"); + nodeUsingAuthFile = besu.createArchiveNodeWithAuthenticationOverWebSocket("node1"); nodeUsingJwtPublicKey = besu.createArchiveNodeWithAuthenticationUsingJwtPublicKeyOverWebSocket("node2"); - cluster.start(node, nodeUsingJwtPublicKey); - node.useWebSocketsForJsonRpc(); + cluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); + nodeUsingAuthFile.useWebSocketsForJsonRpc(); nodeUsingJwtPublicKey.useWebSocketsForJsonRpc(); } @Test public void shouldFailLoginWithWrongCredentials() { - node.verify(login.failure("user", "badpassword")); + nodeUsingAuthFile.verify(login.failure("user", "badpassword")); } @Test public void shouldSucceedLoginWithCorrectCredentials() { - node.verify(login.success("user", "pegasys")); + nodeUsingAuthFile.verify(login.success("user", "pegasys")); } @Test public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { final String token = - node.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); - node.useAuthenticationTokenInHeaderForJsonRpc(token); - node.verify(net.awaitPeerCount(1)); - node.verify(net.netVersionUnauthorizedResponse()); + nodeUsingAuthFile.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); + nodeUsingAuthFile.verify(net.awaitPeerCount(1)); + nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); } @Test From 4c3b89ad2c5e5c56dd7190ca2d31aaf5ad3cc9b7 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 15:23:32 +1000 Subject: [PATCH 12/38] spotless Signed-off-by: Jason Frame --- .../acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java | 3 ++- .../jsonrpc/WebsocketServiceLoginAcceptanceTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 342864a6ab9..82f7a7b1bf6 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -66,7 +66,8 @@ public void shouldSucceedLoginWithCorrectCredentials() { @Test public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { final String token = - nodeUsingAuthFile.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.execute( + permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.awaitPeerCount(1)); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index dd3c44f5fa8..aab2e330f06 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -58,7 +58,8 @@ public void shouldSucceedLoginWithCorrectCredentials() { @Test public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { final String token = - nodeUsingAuthFile.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.execute( + permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.awaitPeerCount(1)); nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); From c769ac30388af9d606d0a57a436521d4c2800e49 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 15:33:12 +1000 Subject: [PATCH 13/38] Fix compile error Signed-off-by: Jason Frame --- .../jsonrpc/authentication/AuthenticationService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 72102d81f10..9d30c609afd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -21,6 +21,7 @@ import java.util.Optional; import javax.annotation.Nullable; +import com.google.common.annotations.VisibleForTesting; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServerResponse; @@ -36,12 +37,16 @@ public class AuthenticationService { private final JWTAuth jwtAuthProvider; + @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; private final Optional credentialAuthProvider; private static final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); private AuthenticationService( - final JWTAuth jwtAuthProvider, final Optional credentialAuthProvider) { + final JWTAuth jwtAuthProvider, + final JWTAuthOptions jwtAuthOptions, + Optional credentialAuthProvider) { this.jwtAuthProvider = jwtAuthProvider; + this.jwtAuthOptions = jwtAuthOptions; this.credentialAuthProvider = credentialAuthProvider; } @@ -103,7 +108,8 @@ private static Optional create( makeCredentialAuthProvider(vertx, authenticationEnabled, authenticationCredentialsFile); return Optional.of( - new AuthenticationService(JWTAuth.create(vertx, jwtAuthOptions), credentialAuthProvider)); + new AuthenticationService( + JWTAuth.create(vertx, jwtAuthOptions), jwtAuthOptions, credentialAuthProvider)); } private static Optional makeCredentialAuthProvider( From 08002332f694c76882cc9537438bc935c7c406a5 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 15:34:12 +1000 Subject: [PATCH 14/38] final Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/AuthenticationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index 9d30c609afd..7e132b8e58e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -44,7 +44,7 @@ public class AuthenticationService { private AuthenticationService( final JWTAuth jwtAuthProvider, final JWTAuthOptions jwtAuthOptions, - Optional credentialAuthProvider) { + final Optional credentialAuthProvider) { this.jwtAuthProvider = jwtAuthProvider; this.jwtAuthOptions = jwtAuthOptions; this.credentialAuthProvider = credentialAuthProvider; From 77f749fbf16ef0879c11b37444d0ac21366fd1a8 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 12 Nov 2019 16:03:02 +1000 Subject: [PATCH 15/38] change besu CLI so that ws can handle JWT auth without credentials file Signed-off-by: Jason Frame --- .../src/main/java/org/hyperledger/besu/cli/BesuCommand.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 5659e88fd03..157eae28d83 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1170,10 +1170,12 @@ private WebSocketConfiguration webSocketConfiguration() { "--rpc-ws-authentication-credentials-file", "--rpc-ws-authentication-public-key-file")); - if (isRpcWsAuthenticationEnabled && rpcWsAuthenticationCredentialsFile() == null) { + if (isRpcWsAuthenticationEnabled + && rpcWsAuthenticationCredentialsFile() == null + && rpcWsAuthenticationPublicKeyFile() == null) { throw new ParameterException( commandLine, - "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file"); + "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); } final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); From 6b9b80fd4a2c079a491136015c15bcf288bc1925 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Wed, 13 Nov 2019 09:11:04 +1000 Subject: [PATCH 16/38] revert unneeded change Signed-off-by: Jason Frame --- .../dsl/transaction/login/LoginUnauthorizedTransaction.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java index 5583a222f61..fd5ddf477a4 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginUnauthorizedTransaction.java @@ -36,8 +36,7 @@ public LoginUnauthorizedTransaction(final String username, final String password @Override public Void execute(final NodeRequests node) { try { - String send = node.login().send(username, password); - Assertions.assertThat(send).isEqualTo("Unauthorized"); + Assertions.assertThat(node.login().send(username, password)).isEqualTo("Unauthorized"); return null; } catch (final IOException e) { fail("Login request failed with exception: %s", e); From d27c814255105300eddec0f0758eb53a28cc867c Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Wed, 13 Nov 2019 09:16:55 +1000 Subject: [PATCH 17/38] update command line help Signed-off-by: Jason Frame --- .../main/java/org/hyperledger/besu/cli/StandaloneCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 75aa3fef812..86c3e29b939 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -69,7 +69,7 @@ class StandaloneCommand implements DefaultCommandValues { names = {"--rpc-http-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "External JSON-RPC HTTP authentication public key use for validating JWT issuer.", + "External JSON-RPC HTTP authentication public key used for validating JWT", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -85,7 +85,7 @@ class StandaloneCommand implements DefaultCommandValues { names = {"--rpc-ws-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "Storage file for JSON-RPC WebSocket authentication credentials (default: ${DEFAULT-VALUE})", + "External JSON-RPC WebSocket authentication public key used for validating JWT", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; From b27f1404a20a31981aef2a85fe5dc93f7d91c94b Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Wed, 13 Nov 2019 11:10:52 +1000 Subject: [PATCH 18/38] additional besu command uts Signed-off-by: Jason Frame --- .../besu/cli/StandaloneCommand.java | 6 ++--- .../hyperledger/besu/cli/BesuCommandTest.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 86c3e29b939..2a106a2efed 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -68,8 +68,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-http-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "External JSON-RPC HTTP authentication public key used for validating JWT", + description = "External JSON-RPC HTTP authentication public key used for validating JWT", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -84,8 +83,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-ws-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "External JSON-RPC WebSocket authentication public key used for validating JWT", + description = "External JSON-RPC WebSocket authentication public key used for validating JWT", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 035fa075ce6..52b58f316ae 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.hyperledger.besu.BesuInfo; @@ -2971,6 +2972,17 @@ public void httpAuthenticationPublicKeyIsConfigured() throws IOException { .isEqualTo(publicKey.toString()); } + @Test + public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() { + parseCommand("--rpc-http-enabled", "--rpc-http-authentication-enabled"); + + verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .contains( + "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); + } + @Test public void wsAuthenticationPublicKeyIsConfigured() throws IOException { final Path publicKey = Files.createTempFile("public_key", ""); @@ -2982,4 +2994,15 @@ public void wsAuthenticationPublicKeyIsConfigured() throws IOException { assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) .isEqualTo(publicKey.toString()); } + + @Test + public void wsAuthenticationWithoutRequiredConfiguredOptionsMustFail() { + parseCommand("--rpc-ws-enabled", "--rpc-ws-authentication-enabled"); + + verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .contains( + "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); + } } From 4e221aa7be47300bd8f1c00ef3da0fc24d66501d Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 09:19:58 +1000 Subject: [PATCH 19/38] PR changes: rename AT var Signed-off-by: Jason Frame --- .../dsl/transaction/login/LoginDisabledTransaction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java index 05ba8384f5a..f4a556aef7c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/login/LoginDisabledTransaction.java @@ -28,8 +28,8 @@ public class LoginDisabledTransaction implements Transaction { @Override public Void execute(final NodeRequests node) { try { - String send = node.login().send("user", "password"); - Assertions.assertThat(send).isEqualTo("Authentication not enabled"); + final String loginResponse = node.login().send("user", "password"); + Assertions.assertThat(loginResponse).isEqualTo("Authentication not enabled"); return null; } catch (final IOException e) { fail("Login request failed with exception: ", e); From c47b0a7a93d4a92391d52f5126f492ef1ae12e14 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 14:41:31 +1000 Subject: [PATCH 20/38] Fail JWT if expiry claim doesn't exist Signed-off-by: Jason Frame --- .../HttpServiceLoginAcceptanceTest.java | 13 +++++++------ .../WebsocketServiceLoginAcceptanceTest.java | 13 +++++++------ .../authentication/AuthenticationUtils.java | 18 +++++++++++++++--- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 82f7a7b1bf6..6a7e87b7b2f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -31,13 +31,14 @@ public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase { private BesuNode nodeUsingAuthFile; private BesuNode nodeUsingJwtPublicKey; + // token with payload{"iat": 1516239022,"exp": 4729363200,"permissions": ["net:peerCount"]} private static final String TOKEN = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjI" - + "sInBlcm1pc3Npb25zIjpbIm5ldDpwZWVyQ291bnQiXX0.fXi73v4UTEO3hiG0AzPaD-OjQy0rL0SY-tMNCfJMdiVde" - + "im7Erwq4sVCrFtmx0tUs-e5Z1t_K-Gx6c_95911T2Jq2VLlwKJDs0FYEGgq2G3W-PMMrT21SPLJM-r7kl9_k51Xbww" - + "D7Cku_JFaLmkhd_l8k-EmGCTCWar514HUTlH0pm4nYhDKa7SuMAqMUo8CSZRCEzSD_AeOShJTk02cPtkCqXzClK3XO" - + "gfxsO5viuklX13VT35lyG-HNNuReLX6U4nWu_irHv0r7Gl8MVFz0Ohm0bA_G1OUh5ue6y7DcYADOoYTmvfSgkKD0hl" - + "bKx3j3tp1PX6Cw_fZUjviFvwxEg"; + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6NDcyOTM2MzIwMCwicGVybWl" + + "zc2lvbnMiOlsibmV0OnBlZXJDb3VudCJdfQ.Y6mNV0nvjzOdqAgMgxknFAOUTKoeRAo4aifNgNrWtuXbJJgz6-" + + "H_0GvLgjlToohPiDZbBJXJJlgb4zzLLB-sRtFnGoPaMgz_d_6z958GjFD7x_Fl0HW-WrTjRNenZNfTyD86OEAf" + + "XHy-7N3OYY2a5yeDbppTJy6nnHTq9hY-ad22-oWL1RbK3T_hnUJII_uXCZ9bJggSfu5m-NNUrm3TeqdnQzIaIz" + + "DqHlL0wNZwVPB4cFGN7zKghReBpkRJ8OFlxexQ491Q5eSpuYquhef-yGCIaMfy7GVtpDSD3Y-hjOErr7gUNCUh" + + "1wlc3Rb7ru_0qNgCWTBPJeRK32GppYotwQ"; @Before public void setUp() throws IOException, URISyntaxException { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index aab2e330f06..a1ac8c62ec2 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -27,13 +27,14 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { private BesuNode nodeUsingAuthFile; private BesuNode nodeUsingJwtPublicKey; + // token with payload{"iat": 1516239022,"exp": 4729363200,"permissions": ["net:peerCount"]} private static final String TOKEN = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjI" - + "sInBlcm1pc3Npb25zIjpbIm5ldDpwZWVyQ291bnQiXX0.fXi73v4UTEO3hiG0AzPaD-OjQy0rL0SY-tMNCfJMdiVde" - + "im7Erwq4sVCrFtmx0tUs-e5Z1t_K-Gx6c_95911T2Jq2VLlwKJDs0FYEGgq2G3W-PMMrT21SPLJM-r7kl9_k51Xbww" - + "D7Cku_JFaLmkhd_l8k-EmGCTCWar514HUTlH0pm4nYhDKa7SuMAqMUo8CSZRCEzSD_AeOShJTk02cPtkCqXzClK3XO" - + "gfxsO5viuklX13VT35lyG-HNNuReLX6U4nWu_irHv0r7Gl8MVFz0Ohm0bA_G1OUh5ue6y7DcYADOoYTmvfSgkKD0hl" - + "bKx3j3tp1PX6Cw_fZUjviFvwxEg"; + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6NDcyOTM2MzIwMCwicGVybWl" + + "zc2lvbnMiOlsibmV0OnBlZXJDb3VudCJdfQ.Y6mNV0nvjzOdqAgMgxknFAOUTKoeRAo4aifNgNrWtuXbJJgz6-" + + "H_0GvLgjlToohPiDZbBJXJJlgb4zzLLB-sRtFnGoPaMgz_d_6z958GjFD7x_Fl0HW-WrTjRNenZNfTyD86OEAf" + + "XHy-7N3OYY2a5yeDbppTJy6nnHTq9hY-ad22-oWL1RbK3T_hnUJII_uXCZ9bJggSfu5m-NNUrm3TeqdnQzIaIz" + + "DqHlL0wNZwVPB4cFGN7zKghReBpkRJ8OFlxexQ491Q5eSpuYquhef-yGCIaMfy7GVtpDSD3Y-hjOErr7gUNCUh" + + "1wlc3Rb7ru_0qNgCWTBPJeRK32GppYotwQ"; @Before public void setUp() throws IOException, URISyntaxException { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java index 16a486c6a70..0238b69a3f9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java @@ -71,7 +71,7 @@ public static void getUser( final String token, final Handler> handler) { try { - if (!authenticationService.isPresent()) { + if (authenticationService.isEmpty()) { handler.handle(Optional.empty()); } else { authenticationService @@ -80,8 +80,14 @@ public static void getUser( .authenticate( new JsonObject().put("jwt", token), (r) -> { - final User user = r.result(); - handler.handle(Optional.of(user)); + if (r.succeeded()) { + final Optional user = Optional.ofNullable(r.result()); + validateExpExists(user); + handler.handle(user); + } else { + LOG.debug("Invalid JWT token", r.cause()); + handler.handle(Optional.empty()); + } }); } } catch (Exception e) { @@ -89,6 +95,12 @@ public static void getUser( } } + private static void validateExpExists(final Optional user) { + if (!user.map(User::principal).map(p -> p.containsKey("exp")).orElse(false)) { + throw new IllegalStateException("Invalid JWT doesn't have expiry"); + } + } + public static String getJwtTokenFromAuthorizationHeaderValue(final String value) { if (value != null) { final String bearerSchemaName = "Bearer "; From a18ccb87c0f3f71ff5790700ef140d58927741ed Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 15:20:27 +1000 Subject: [PATCH 21/38] use pem file format Signed-off-by: Jason Frame --- .../test/resources/authentication/jwt_public_key | 4 +++- ethereum/api/build.gradle | 1 + .../authentication/JWTAuthOptionsFactory.java | 15 ++++++++++----- .../authentication/JWTAuthOptionsFactoryTest.java | 15 ++++++++------- .../test/resources/authentication/jwt_public_key | 9 +++++++++ 5 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 ethereum/api/src/test/resources/authentication/jwt_public_key diff --git a/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key b/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key index c0c4559808e..9107cf29702 100644 --- a/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key +++ b/acceptance-tests/tests/src/test/resources/authentication/jwt_public_key @@ -1,7 +1,9 @@ +-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6tMhjogMulRbYby7bCL rFhukDnxvm4XR3KSXLKdLLQHHyouMOQaLac9M+/Z1KkIpqfZPjLfW2/yUg2IKx4T dvFVzbVq17X6dq49ZS8jJtb8l2+Vius4d3LnpvxCOematRG9Acn+2qLwC+sK7RPY OxEqKPU5LNBH1C0FfviazY5jkixBFICzIq/SyyRnGX+iIONnNsu0TlhWVLSlZbg5 NYf4cAzu/1d5MgspyZwnRo468gqaak3wQzkmk69Z25L1N7TXZvk2b7rT7/ssFnt+ //fKVpD6qkQ3OopD+7gOziAYUxChw6RUWekV+uRgNADQhaqV6wDdogBz77wTJedV -YwIDAQAB \ No newline at end of file +YwIDAQAB +-----END PUBLIC KEY----- diff --git a/ethereum/api/build.gradle b/ethereum/api/build.gradle index a36486030ee..bd4f690f770 100644 --- a/ethereum/api/build.gradle +++ b/ethereum/api/build.gradle @@ -50,6 +50,7 @@ dependencies { implementation 'io.vertx:vertx-web' implementation 'io.vertx:vertx-auth-jwt' implementation 'io.vertx:vertx-unit' + implementation 'org.bouncycastle:bcprov-jdk15on' testImplementation 'com.squareup.okhttp3:okhttp' testImplementation 'junit:junit' diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 8c645b8b4e0..c0647e2bb7a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -15,8 +15,9 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.nio.file.Files; +import java.io.InputStreamReader; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -24,6 +25,7 @@ import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.jwt.JWTAuthOptions; +import org.bouncycastle.util.io.pem.PemReader; public class JWTAuthOptionsFactory { @@ -31,11 +33,12 @@ public class JWTAuthOptionsFactory { private static final String PERMISSIONS = "permissions"; public JWTAuthOptions createForExternalPublicKey(final File externalPublicKeyFile) { - final String externalJwtPublicKey = readPublicKey(externalPublicKeyFile); + final byte[] externalJwtPublicKey = readPublicKey(externalPublicKeyFile); + final String base64EncodedPublicKey = Base64.getEncoder().encodeToString(externalJwtPublicKey); return new JWTAuthOptions() .setPermissionsClaimKey(PERMISSIONS) .addPubSecKey( - new PubSecKeyOptions().setAlgorithm(ALGORITHM).setPublicKey(externalJwtPublicKey)); + new PubSecKeyOptions().setAlgorithm(ALGORITHM).setPublicKey(base64EncodedPublicKey)); } public JWTAuthOptions createWithGeneratedKeyPair() { @@ -50,9 +53,11 @@ public JWTAuthOptions createWithGeneratedKeyPair() { Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); } - private String readPublicKey(final File authenticationPublicKeyFile) { + private byte[] readPublicKey(final File authenticationPublicKeyFile) { try { - return Files.readString(authenticationPublicKeyFile.toPath()); + final PemReader pemReader = + new PemReader(new InputStreamReader(new FileInputStream(authenticationPublicKeyFile))); + return pemReader.readPemObject().getContent(); } catch (IOException e) { throw new IllegalStateException("Authentication RPC public key could not be read", e); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java index fcb448ba80c..263c99e8fe0 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java @@ -18,9 +18,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.net.URISyntaxException; +import java.nio.file.Paths; import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.jwt.JWTAuthOptions; @@ -62,13 +61,15 @@ public void createsOptionsWithGeneratedKeyPairThatIsDifferentEachTime() { } @Test - public void createsOptionsUsingPublicKeyFile() throws IOException { + public void createsOptionsUsingPublicKeyFile() throws URISyntaxException { final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); - final Path enclavePublicKey = Files.createTempFile("enclave", "pub"); - Files.writeString(enclavePublicKey, JWT_PUBLIC_KEY); + final File enclavePublicKeyFile = + Paths.get(ClassLoader.getSystemResource("authentication/jwt_public_key").toURI()) + .toAbsolutePath() + .toFile(); final JWTAuthOptions jwtAuthOptions = - jwtAuthOptionsFactory.createForExternalPublicKey(enclavePublicKey.toFile()); + jwtAuthOptionsFactory.createForExternalPublicKey(enclavePublicKeyFile); assertThat(jwtAuthOptions.getPubSecKeys()).hasSize(1); assertThat(jwtAuthOptions.getPubSecKeys().get(0).getAlgorithm()).isEqualTo("RS256"); assertThat(jwtAuthOptions.getPubSecKeys().get(0).getSecretKey()).isNull(); diff --git a/ethereum/api/src/test/resources/authentication/jwt_public_key b/ethereum/api/src/test/resources/authentication/jwt_public_key new file mode 100644 index 00000000000..9107cf29702 --- /dev/null +++ b/ethereum/api/src/test/resources/authentication/jwt_public_key @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6tMhjogMulRbYby7bCL +rFhukDnxvm4XR3KSXLKdLLQHHyouMOQaLac9M+/Z1KkIpqfZPjLfW2/yUg2IKx4T +dvFVzbVq17X6dq49ZS8jJtb8l2+Vius4d3LnpvxCOematRG9Acn+2qLwC+sK7RPY +OxEqKPU5LNBH1C0FfviazY5jkixBFICzIq/SyyRnGX+iIONnNsu0TlhWVLSlZbg5 +NYf4cAzu/1d5MgspyZwnRo468gqaak3wQzkmk69Z25L1N7TXZvk2b7rT7/ssFnt+ +//fKVpD6qkQ3OopD+7gOziAYUxChw6RUWekV+uRgNADQhaqV6wDdogBz77wTJedV +YwIDAQAB +-----END PUBLIC KEY----- From 4b287d9dc89fc7c20a1fc77ff26632bb94b1f0cb Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 15:29:43 +1000 Subject: [PATCH 22/38] handle invalid pem file format Signed-off-by: Jason Frame --- .../authentication/JWTAuthOptionsFactory.java | 7 ++++++- .../authentication/JWTAuthOptionsFactoryTest.java | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index c0647e2bb7a..8089da6e04c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -25,6 +25,7 @@ import io.vertx.ext.auth.PubSecKeyOptions; import io.vertx.ext.auth.jwt.JWTAuthOptions; +import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; public class JWTAuthOptionsFactory { @@ -57,7 +58,11 @@ private byte[] readPublicKey(final File authenticationPublicKeyFile) { try { final PemReader pemReader = new PemReader(new InputStreamReader(new FileInputStream(authenticationPublicKeyFile))); - return pemReader.readPemObject().getContent(); + final PemObject pemObject = pemReader.readPemObject(); + if (pemObject == null) { + throw new IllegalStateException("Authentication RPC public key file format is invalid"); + } + return pemObject.getContent(); } catch (IOException e) { throw new IllegalStateException("Authentication RPC public key could not be read", e); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java index 263c99e8fe0..77d9406938f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactoryTest.java @@ -18,7 +18,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.File; +import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import io.vertx.ext.auth.PubSecKeyOptions; @@ -85,4 +88,16 @@ public void failsToCreateOptionsWhenPublicKeyFileDoesNotExist() { .isInstanceOf(IllegalStateException.class) .hasMessage("Authentication RPC public key could not be read"); } + + @Test + public void failsToCreateOptionsWhenPublicKeyFileIsInvalid() throws IOException { + final JWTAuthOptionsFactory jwtAuthOptionsFactory = new JWTAuthOptionsFactory(); + final Path enclavePublicKey = Files.createTempFile("enclave", "pub"); + Files.writeString(enclavePublicKey, "invalidDataNo---HeadersAndNotBase64"); + + assertThatThrownBy( + () -> jwtAuthOptionsFactory.createForExternalPublicKey(enclavePublicKey.toFile())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Authentication RPC public key file format is invalid"); + } } From 7584013755e2fb3f7918e896ef3a83585a2046f1 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 15:35:29 +1000 Subject: [PATCH 23/38] update cli description to mention file is in pem format Signed-off-by: Jason Frame --- .../main/java/org/hyperledger/besu/cli/StandaloneCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 2a106a2efed..bdefbfc70c2 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -68,7 +68,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-http-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "External JSON-RPC HTTP authentication public key used for validating JWT", + description = "External JSON-RPC HTTP authentication public key file in PEM format used for validating JWT", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -83,7 +83,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-ws-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "External JSON-RPC WebSocket authentication public key used for validating JWT", + description = "External JSON-RPC WebSocket authentication public key file in PEM format used for validating JWT", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; From 014d2fef1d6284c7301fc37e138427dce657e392 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 17:05:42 +1000 Subject: [PATCH 24/38] spotless Signed-off-by: Jason Frame --- .../java/org/hyperledger/besu/cli/StandaloneCommand.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index bdefbfc70c2..8cf0e5eb1bc 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -68,7 +68,8 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-http-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "External JSON-RPC HTTP authentication public key file in PEM format used for validating JWT", + description = + "External JSON-RPC HTTP authentication public key file in PEM format used for validating JWT", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -83,7 +84,8 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-ws-authentication-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "External JSON-RPC WebSocket authentication public key file in PEM format used for validating JWT", + description = + "External JSON-RPC WebSocket authentication public key file in PEM format used for validating JWT", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; From 88631adc40273119baaadff92b4be72ca2aee290 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 17:05:54 +1000 Subject: [PATCH 25/38] additional unit tests Signed-off-by: Jason Frame --- .../AuthenticationUtilsTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtilsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtilsTest.java index 74b387f8782..b33c2d7cdde 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtilsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtilsTest.java @@ -15,11 +15,30 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.util.Optional; + +import io.vertx.core.Handler; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.User; +import io.vertx.ext.auth.jwt.JWTAuth; +import io.vertx.ext.auth.jwt.JWTAuthOptions; +import io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl; import org.junit.Test; public class AuthenticationUtilsTest { + private static String INVALID_TOKEN_WITHOUT_EXP = + "ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn" + + "0.eyJpYXQiOjE1MTYyMzkwMjIsInBlcm1pc3Npb25zIjpbIm5ldDpwZWVyQ291bnQiXX0"; + private static String VALID_TOKEN = + "ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.eyJpYXQiOjE1" + + "MTYyMzkwMjIsImV4cCI6NDcyOTM2MzIwMCwicGVybWlzc2lvbnMiOlsibmV0OnBlZXJDb3VudCJdfQ"; + private static String VALID_TOKEN_DECODED_PAYLOAD = + "{\"iat\": 1516239022,\"exp\": 4729363200," + "\"permissions\": [\"net:peerCount\"]}"; + @Test public void getJwtTokenFromNullStringShouldReturnNull() { final String headerValue = null; @@ -55,4 +74,43 @@ public void getJwtTokenFromValidAuthorizationHeaderValueShouldReturnToken() { assertThat(token).isEqualTo("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"); } + + @Test + public void getUserFailsIfTokenDoesNotHaveExpiryClaim() { + final AuthenticationService authenticationService = mock(AuthenticationService.class); + final JWTAuth jwtAuth = new JWTAuthProviderImpl(null, new JWTAuthOptions()); + final StubUserHandler handler = new StubUserHandler(); + when(authenticationService.getJwtAuthProvider()).thenReturn(jwtAuth); + + AuthenticationUtils.getUser( + Optional.of(authenticationService), INVALID_TOKEN_WITHOUT_EXP, handler); + + assertThat(handler.getEvent()).isEmpty(); + } + + @Test + public void getUserSucceedsWithValidToken() { + final AuthenticationService authenticationService = mock(AuthenticationService.class); + final JWTAuth jwtAuth = new JWTAuthProviderImpl(null, new JWTAuthOptions()); + final StubUserHandler handler = new StubUserHandler(); + when(authenticationService.getJwtAuthProvider()).thenReturn(jwtAuth); + + AuthenticationUtils.getUser(Optional.of(authenticationService), VALID_TOKEN, handler); + + assertThat(handler.getEvent().get().principal()) + .isEqualTo(new JsonObject(VALID_TOKEN_DECODED_PAYLOAD)); + } + + private static class StubUserHandler implements Handler> { + private Optional event; + + @Override + public void handle(final Optional event) { + this.event = event; + } + + public Optional getEvent() { + return event; + } + } } From bf29286535b785d7ca9a0a41354320a2454da6dd Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 14 Nov 2019 17:12:30 +1000 Subject: [PATCH 26/38] default charset warning Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/JWTAuthOptionsFactory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 8089da6e04c..d1e4efa1a7a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -57,7 +59,8 @@ public JWTAuthOptions createWithGeneratedKeyPair() { private byte[] readPublicKey(final File authenticationPublicKeyFile) { try { final PemReader pemReader = - new PemReader(new InputStreamReader(new FileInputStream(authenticationPublicKeyFile))); + new PemReader( + new InputStreamReader(new FileInputStream(authenticationPublicKeyFile), UTF_8)); final PemObject pemObject = pemReader.readPemObject(); if (pemObject == null) { throw new IllegalStateException("Authentication RPC public key file format is invalid"); From 3e4dd660e5769a758f66278d2d03e0e0a3386def Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 09:13:25 +1000 Subject: [PATCH 27/38] PR changes: simplify AT authenticated node creation Signed-off-by: Jason Frame --- .../node/configuration/BesuNodeFactory.java | 24 +++---------------- .../HttpServiceLoginAcceptanceTest.java | 4 ++-- .../WebsocketServiceLoginAcceptanceTest.java | 24 +++++++++++++++---- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index 61870ab9fc6..1707e047a35 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -128,7 +128,7 @@ public BesuNode createArchiveNodeNetServicesDisabled(final String name) throws I .build()); } - public BesuNode createArchiveNodeWithAuthentication(final String name) + public BesuNode createNodeWithAuthentication(final String name) throws IOException, URISyntaxException { return create( new BesuNodeConfigurationBuilder() @@ -136,10 +136,11 @@ public BesuNode createArchiveNodeWithAuthentication(final String name) .jsonRpcEnabled() .jsonRpcAuthenticationEnabled() .webSocketEnabled() + .webSocketAuthenticationEnabled() .build()); } - public BesuNode createArchiveNodeWithAuthenticationUsingJwtPublicKey(final String name) + public BesuNode createNodeWithAuthenticationUsingJwtPublicKey(final String name) throws IOException, URISyntaxException { return create( new BesuNodeConfigurationBuilder() @@ -147,25 +148,6 @@ public BesuNode createArchiveNodeWithAuthenticationUsingJwtPublicKey(final Strin .jsonRpcEnabled() .jsonRpcAuthenticationUsingPublicKeyEnabled() .webSocketEnabled() - .build()); - } - - public BesuNode createArchiveNodeWithAuthenticationOverWebSocket(final String name) - throws IOException, URISyntaxException { - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .webSocketEnabled() - .webSocketAuthenticationEnabled() - .build()); - } - - public BesuNode createArchiveNodeWithAuthenticationUsingJwtPublicKeyOverWebSocket( - final String name) throws IOException, URISyntaxException { - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .webSocketEnabled() .webSocketAuthenticationUsingPublicKeyEnabled() .build()); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 6a7e87b7b2f..4dd54936d58 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -46,8 +46,8 @@ public void setUp() throws IOException, URISyntaxException { new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); authenticatedCluster = new Cluster(clusterConfiguration, net); - nodeUsingAuthFile = besu.createArchiveNodeWithAuthentication("node1"); - nodeUsingJwtPublicKey = besu.createArchiveNodeWithAuthenticationUsingJwtPublicKey("node2"); + nodeUsingAuthFile = besu.createNodeWithAuthentication("node1"); + nodeUsingJwtPublicKey = besu.createNodeWithAuthenticationUsingJwtPublicKey("node2"); authenticatedCluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); nodeUsingAuthFile.verify(login.awaitResponse("user", "badpassword")); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index a1ac8c62ec2..2a3b5df49c9 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -16,6 +16,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; import java.io.IOException; import java.net.URISyntaxException; @@ -26,6 +29,7 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { private BesuNode nodeUsingAuthFile; private BesuNode nodeUsingJwtPublicKey; + private Cluster authenticatedCluster; // token with payload{"iat": 1516239022,"exp": 4729363200,"permissions": ["net:peerCount"]} private static final String TOKEN = @@ -38,12 +42,18 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { @Before public void setUp() throws IOException, URISyntaxException { - nodeUsingAuthFile = besu.createArchiveNodeWithAuthenticationOverWebSocket("node1"); - nodeUsingJwtPublicKey = - besu.createArchiveNodeWithAuthenticationUsingJwtPublicKeyOverWebSocket("node2"); - cluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); + final ClusterConfiguration clusterConfiguration = + new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); + authenticatedCluster = new Cluster(clusterConfiguration, net); + + nodeUsingAuthFile = besu.createNodeWithAuthentication("node1"); + nodeUsingJwtPublicKey = besu.createNodeWithAuthenticationUsingJwtPublicKey("node2"); nodeUsingAuthFile.useWebSocketsForJsonRpc(); nodeUsingJwtPublicKey.useWebSocketsForJsonRpc(); + authenticatedCluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); + + nodeUsingAuthFile.verify(login.awaitResponse("user", "badpassword")); + nodeUsingJwtPublicKey.verify(login.awaitResponse("user", "badpassword")); } @Test @@ -78,4 +88,10 @@ public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { public void loginShouldBeDisabledWhenUsingExternalJwtPublicKey() { nodeUsingJwtPublicKey.verify(login.disabled()); } + + @Override + public void tearDownAcceptanceTestBase() { + authenticatedCluster.stop(); + super.tearDownAcceptanceTestBase(); + } } From 7e4351a14965a5d1bb4e88356bc1270055021945 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 09:22:02 +1000 Subject: [PATCH 28/38] PR changes: split failure AT tests out into separate tests Signed-off-by: Jason Frame --- .../HttpServiceLoginAcceptanceTest.java | 15 +++++++++++++-- .../WebsocketServiceLoginAcceptanceTest.java | 19 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 4dd54936d58..a3f0d44f898 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -70,16 +70,27 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { nodeUsingAuthFile.execute( permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); - nodeUsingAuthFile.verify(net.awaitPeerCount(1)); + } + + @Test + public void jsonRpcMethodShouldFailOnNonPermittedMethod() { + final String token = + nodeUsingAuthFile.execute( + permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); - nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); + } + + @Test + public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldFailOnNonPermittedMethod() { + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index 2a3b5df49c9..0dec4f694fe 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -48,10 +48,10 @@ public void setUp() throws IOException, URISyntaxException { nodeUsingAuthFile = besu.createNodeWithAuthentication("node1"); nodeUsingJwtPublicKey = besu.createNodeWithAuthenticationUsingJwtPublicKey("node2"); - nodeUsingAuthFile.useWebSocketsForJsonRpc(); - nodeUsingJwtPublicKey.useWebSocketsForJsonRpc(); authenticatedCluster.start(nodeUsingAuthFile, nodeUsingJwtPublicKey); + nodeUsingAuthFile.useWebSocketsForJsonRpc(); + nodeUsingJwtPublicKey.useWebSocketsForJsonRpc(); nodeUsingAuthFile.verify(login.awaitResponse("user", "badpassword")); nodeUsingJwtPublicKey.verify(login.awaitResponse("user", "badpassword")); } @@ -73,17 +73,30 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.awaitPeerCount(1)); + } + + @Test + public void jsonRpcMethodShouldFailOnNonPermittedMethod() { + final String token = + nodeUsingAuthFile.execute( + permissioningTransactions.createSuccessfulLogin("user", "pegasys")); + nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); - nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedResponse()); } + @Test + public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldFailOnNonPermittedMethod() { + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedResponse()); + } + @Test public void loginShouldBeDisabledWhenUsingExternalJwtPublicKey() { nodeUsingJwtPublicKey.verify(login.disabled()); From e13b9dbffd563448d9b6f6759cc3ace2a54f8e9d Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 09:34:47 +1000 Subject: [PATCH 29/38] fix closing of pem reading file stream Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/JWTAuthOptionsFactory.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index d1e4efa1a7a..e624ca7a0de 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -57,10 +57,9 @@ public JWTAuthOptions createWithGeneratedKeyPair() { } private byte[] readPublicKey(final File authenticationPublicKeyFile) { - try { - final PemReader pemReader = - new PemReader( - new InputStreamReader(new FileInputStream(authenticationPublicKeyFile), UTF_8)); + try (InputStreamReader keyFileReader = + new InputStreamReader(new FileInputStream(authenticationPublicKeyFile), UTF_8)) { + final PemReader pemReader = new PemReader(keyFileReader); final PemObject pemObject = pemReader.readPemObject(); if (pemObject == null) { throw new IllegalStateException("Authentication RPC public key file format is invalid"); From c4caf32cd7f9cf9636787285e3dd5974a29aee44 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 10:13:45 +1000 Subject: [PATCH 30/38] fix closing of pem reading file stream Signed-off-by: Jason Frame --- .../jsonrpc/authentication/JWTAuthOptionsFactory.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index e624ca7a0de..16c1dee6013 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -16,10 +16,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; +import java.nio.file.Files; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -56,10 +56,9 @@ public JWTAuthOptions createWithGeneratedKeyPair() { Base64.getEncoder().encodeToString(keypair.getPrivate().getEncoded()))); } - private byte[] readPublicKey(final File authenticationPublicKeyFile) { - try (InputStreamReader keyFileReader = - new InputStreamReader(new FileInputStream(authenticationPublicKeyFile), UTF_8)) { - final PemReader pemReader = new PemReader(keyFileReader); + private byte[] readPublicKey(final File publicKeyFile) { + try (BufferedReader reader = Files.newBufferedReader(publicKeyFile.toPath(), UTF_8); + PemReader pemReader = new PemReader(reader); ) { final PemObject pemObject = pemReader.readPemObject(); if (pemObject == null) { throw new IllegalStateException("Authentication RPC public key file format is invalid"); From e3154f12f5775e889fd292a30a8396853c305c17 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 10:15:01 +1000 Subject: [PATCH 31/38] finals Signed-off-by: Jason Frame --- .../api/jsonrpc/authentication/JWTAuthOptionsFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 16c1dee6013..2be5695dc88 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -57,8 +57,8 @@ public JWTAuthOptions createWithGeneratedKeyPair() { } private byte[] readPublicKey(final File publicKeyFile) { - try (BufferedReader reader = Files.newBufferedReader(publicKeyFile.toPath(), UTF_8); - PemReader pemReader = new PemReader(reader); ) { + try (final BufferedReader reader = Files.newBufferedReader(publicKeyFile.toPath(), UTF_8); + final PemReader pemReader = new PemReader(reader)) { final PemObject pemObject = pemReader.readPemObject(); if (pemObject == null) { throw new IllegalStateException("Authentication RPC public key file format is invalid"); From 0b98878325952f6fd08ce1a4d4310dbb7509f2ca Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 15:14:08 +1000 Subject: [PATCH 32/38] PR changes for http login AT Signed-off-by: Jason Frame --- ...n.java => ExpectUnauthorizedException.java} | 14 ++++++-------- .../dsl/condition/net/NetConditions.java | 8 ++++++-- .../HttpServiceLoginAcceptanceTest.java | 18 +++++++++++++----- .../WebsocketServiceLoginAcceptanceTest.java | 1 + 4 files changed, 26 insertions(+), 15 deletions(-) rename acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/{ExpectNetVersionPermissionException.java => ExpectUnauthorizedException.java} (73%) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java similarity index 73% rename from acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java rename to acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java index 56bdc0fc59f..5f511d36a34 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionException.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java @@ -19,19 +19,17 @@ import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetVersionTransaction; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; import org.web3j.protocol.exceptions.ClientConnectionException; -public class ExpectNetVersionPermissionException implements Condition { +public class ExpectUnauthorizedException implements Condition { - private final NetVersionTransaction transaction; - private final String expectedMessage; + private static final String UNAUTHORIZED = "Unauthorized"; + private final Transaction transaction; - public ExpectNetVersionPermissionException( - final NetVersionTransaction transaction, final String expectedMessage) { + public ExpectUnauthorizedException(final Transaction transaction) { this.transaction = transaction; - this.expectedMessage = expectedMessage; } @Override @@ -41,6 +39,6 @@ public void verify(final Node node) { final Throwable cause = thrown.getCause(); assertThat(cause).isInstanceOf(ClientConnectionException.class); - assertThat(cause.getMessage()).contains(expectedMessage); + assertThat(cause.getMessage()).contains(UNAUTHORIZED); } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java index 39044410241..84efc83e259 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java @@ -35,6 +35,10 @@ public Condition netServicesOnlyJsonRpcEnabled() { return new ExpectNetServicesReturnsOnlyJsonRpcActive(transactions.netServices()); } + public Condition netServicesUnauthorizedExceptional() { + return new ExpectUnauthorizedException(transactions.netServices()); + } + public Condition netVersion() { return new ExpectNetVersionIsNotBlank(transactions.netVersion()); } @@ -51,8 +55,8 @@ public Condition netVersionExceptional(final Class cause) { return new ExpectNetVersionConnectionExceptionWithCause(transactions.netVersion(), cause); } - public Condition netVersionUnauthorizedExceptional(final String expectedMessage) { - return new ExpectNetVersionPermissionException(transactions.netVersion(), expectedMessage); + public Condition netVersionUnauthorizedExceptional() { + return new ExpectUnauthorizedException(transactions.netVersion()); } public Condition netVersionUnauthorizedResponse() { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index a3f0d44f898..5ea5223ce18 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -32,7 +32,7 @@ public class HttpServiceLoginAcceptanceTest extends AcceptanceTestBase { private BesuNode nodeUsingJwtPublicKey; // token with payload{"iat": 1516239022,"exp": 4729363200,"permissions": ["net:peerCount"]} - private static final String TOKEN = + private static final String TOKEN_ALLOWING_NET_PEER_COUNT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6NDcyOTM2MzIwMCwicGVybWl" + "zc2lvbnMiOlsibmV0OnBlZXJDb3VudCJdfQ.Y6mNV0nvjzOdqAgMgxknFAOUTKoeRAo4aifNgNrWtuXbJJgz6-" + "H_0GvLgjlToohPiDZbBJXJJlgb4zzLLB-sRtFnGoPaMgz_d_6z958GjFD7x_Fl0HW-WrTjRNenZNfTyD86OEAf" @@ -79,19 +79,27 @@ public void jsonRpcMethodShouldFailOnNonPermittedMethod() { nodeUsingAuthFile.execute( permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); - nodeUsingAuthFile.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); + nodeUsingAuthFile.verify(net.netVersionUnauthorizedExceptional()); + nodeUsingAuthFile.verify(net.netServicesUnauthorizedExceptional()); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { - nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN_ALLOWING_NET_PEER_COUNT); nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldFailOnNonPermittedMethod() { - nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); - nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional("Unauthorized")); + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN_ALLOWING_NET_PEER_COUNT); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional()); + nodeUsingJwtPublicKey.verify(net.netServicesUnauthorizedExceptional()); + } + + @Test + public void jsonRpcMethodShouldFailWhenThereIsNoToken() { + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional()); + nodeUsingJwtPublicKey.verify(net.netServicesUnauthorizedExceptional()); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index 0dec4f694fe..ffc716bacb9 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -73,6 +73,7 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.awaitPeerCount(1)); + nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); } @Test From eef961057f161412878bc90d91ccf0558fab826b Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 15:45:53 +1000 Subject: [PATCH 33/38] PR changes for ws login AT Signed-off-by: Jason Frame --- ...etVersionConnectionExceptionWithCause.java | 40 ------------------- ...PermissionJsonRpcUnauthorizedResponse.java | 34 ---------------- ...Exception.java => ExpectUnauthorized.java} | 12 ++---- .../dsl/condition/net/NetConditions.java | 16 ++------ .../net/NetServicesTransaction.java | 3 ++ .../net/NetVersionTransaction.java | 3 ++ .../HttpServiceLoginAcceptanceTest.java | 12 +++--- .../WebsocketServiceLoginAcceptanceTest.java | 20 ++++++---- 8 files changed, 32 insertions(+), 108 deletions(-) delete mode 100644 acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionConnectionExceptionWithCause.java delete mode 100644 acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java rename acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/{ExpectUnauthorizedException.java => ExpectUnauthorized.java} (74%) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionConnectionExceptionWithCause.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionConnectionExceptionWithCause.java deleted file mode 100644 index 7dbc96bf390..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionConnectionExceptionWithCause.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * 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.tests.acceptance.dsl.condition.net; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetVersionTransaction; - -public class ExpectNetVersionConnectionExceptionWithCause implements Condition { - - private final NetVersionTransaction transaction; - private final Class cause; - - public ExpectNetVersionConnectionExceptionWithCause( - final NetVersionTransaction transaction, final Class cause) { - this.transaction = transaction; - this.cause = cause; - } - - @Override - public void verify(final Node node) { - final Throwable thrown = catchThrowable(() -> node.execute(transaction)); - assertThat(thrown).isInstanceOf(cause); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java deleted file mode 100644 index 91e5ecfd630..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * 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.tests.acceptance.dsl.condition.net; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetVersionTransaction; - -public class ExpectNetVersionPermissionJsonRpcUnauthorizedResponse implements Condition { - - private final NetVersionTransaction transaction; - - public ExpectNetVersionPermissionJsonRpcUnauthorizedResponse( - final NetVersionTransaction transaction) { - this.transaction = transaction; - } - - @Override - public void verify(final Node node) { - node.execute(transaction); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java similarity index 74% rename from acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java rename to acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java index 5f511d36a34..b532d14dcf2 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorizedException.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java @@ -21,24 +21,18 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.web3j.protocol.exceptions.ClientConnectionException; - -public class ExpectUnauthorizedException implements Condition { +public class ExpectUnauthorized implements Condition { private static final String UNAUTHORIZED = "Unauthorized"; private final Transaction transaction; - public ExpectUnauthorizedException(final Transaction transaction) { + public ExpectUnauthorized(final Transaction transaction) { this.transaction = transaction; } @Override public void verify(final Node node) { final Throwable thrown = catchThrowable(() -> node.execute(transaction)); - assertThat(thrown).isInstanceOf(RuntimeException.class); - - final Throwable cause = thrown.getCause(); - assertThat(cause).isInstanceOf(ClientConnectionException.class); - assertThat(cause.getMessage()).contains(UNAUTHORIZED); + assertThat(thrown.getMessage()).contains(UNAUTHORIZED); } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java index 84efc83e259..004b12b9162 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/NetConditions.java @@ -35,8 +35,8 @@ public Condition netServicesOnlyJsonRpcEnabled() { return new ExpectNetServicesReturnsOnlyJsonRpcActive(transactions.netServices()); } - public Condition netServicesUnauthorizedExceptional() { - return new ExpectUnauthorizedException(transactions.netServices()); + public Condition netServicesUnauthorized() { + return new ExpectUnauthorized(transactions.netServices()); } public Condition netVersion() { @@ -51,16 +51,8 @@ public Condition netVersionExceptional(final String expectedMessage) { return new ExpectNetVersionConnectionException(transactions.netVersion(), expectedMessage); } - public Condition netVersionExceptional(final Class cause) { - return new ExpectNetVersionConnectionExceptionWithCause(transactions.netVersion(), cause); - } - - public Condition netVersionUnauthorizedExceptional() { - return new ExpectUnauthorizedException(transactions.netVersion()); - } - - public Condition netVersionUnauthorizedResponse() { - return new ExpectNetVersionPermissionJsonRpcUnauthorizedResponse(transactions.netVersion()); + public Condition netVersionUnauthorized() { + return new ExpectUnauthorized(transactions.netVersion()); } public Condition awaitPeerCountExceptional() { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java index f4b87f45af1..2b3386a0d91 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetServicesTransaction.java @@ -37,6 +37,9 @@ public Map> execute(final NodeRequests requestFactor } catch (final Exception e) { throw new RuntimeException(e); } + if (netServicesResponse.hasError()) { + throw new RuntimeException(netServicesResponse.getError().getMessage()); + } return netServicesResponse.getResult(); } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java index 6cb7950ad47..8d4db1397cb 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java @@ -30,6 +30,9 @@ public String execute(final NodeRequests node) { try { final NetVersion result = node.net().netVersion().send(); assertThat(result).isNotNull(); + if (result.hasError()) { + throw new RuntimeException(result.getError().getMessage()); + } return result.getNetVersion(); } catch (final Exception e) { throw new RuntimeException(e); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java index 5ea5223ce18..5ff7dd6d3ac 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/HttpServiceLoginAcceptanceTest.java @@ -79,8 +79,8 @@ public void jsonRpcMethodShouldFailOnNonPermittedMethod() { nodeUsingAuthFile.execute( permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); - nodeUsingAuthFile.verify(net.netVersionUnauthorizedExceptional()); - nodeUsingAuthFile.verify(net.netServicesUnauthorizedExceptional()); + nodeUsingAuthFile.verify(net.netVersionUnauthorized()); + nodeUsingAuthFile.verify(net.netServicesUnauthorized()); } @Test @@ -92,14 +92,14 @@ public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldFailOnNonPermittedMethod() { nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN_ALLOWING_NET_PEER_COUNT); - nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional()); - nodeUsingJwtPublicKey.verify(net.netServicesUnauthorizedExceptional()); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorized()); + nodeUsingJwtPublicKey.verify(net.netServicesUnauthorized()); } @Test public void jsonRpcMethodShouldFailWhenThereIsNoToken() { - nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedExceptional()); - nodeUsingJwtPublicKey.verify(net.netServicesUnauthorizedExceptional()); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorized()); + nodeUsingJwtPublicKey.verify(net.netServicesUnauthorized()); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index ffc716bacb9..e81cdad8647 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -32,7 +32,7 @@ public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase { private Cluster authenticatedCluster; // token with payload{"iat": 1516239022,"exp": 4729363200,"permissions": ["net:peerCount"]} - private static final String TOKEN = + private static final String TOKEN_ALLOWING_NET_PEER_COUNT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6NDcyOTM2MzIwMCwicGVybWl" + "zc2lvbnMiOlsibmV0OnBlZXJDb3VudCJdfQ.Y6mNV0nvjzOdqAgMgxknFAOUTKoeRAo4aifNgNrWtuXbJJgz6-" + "H_0GvLgjlToohPiDZbBJXJJlgb4zzLLB-sRtFnGoPaMgz_d_6z958GjFD7x_Fl0HW-WrTjRNenZNfTyD86OEAf" @@ -73,7 +73,6 @@ public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); nodeUsingAuthFile.verify(net.awaitPeerCount(1)); - nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); } @Test @@ -82,20 +81,27 @@ public void jsonRpcMethodShouldFailOnNonPermittedMethod() { nodeUsingAuthFile.execute( permissioningTransactions.createSuccessfulLogin("user", "pegasys")); nodeUsingAuthFile.useAuthenticationTokenInHeaderForJsonRpc(token); - nodeUsingAuthFile.verify(net.netVersionUnauthorizedResponse()); + nodeUsingAuthFile.verify(net.netVersionUnauthorized()); + nodeUsingAuthFile.verify(net.netServicesUnauthorized()); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldSucceed() { - nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN_ALLOWING_NET_PEER_COUNT); nodeUsingJwtPublicKey.verify(net.awaitPeerCount(1)); - nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedResponse()); } @Test public void externalJwtPublicKeyUsedOnJsonRpcMethodShouldFailOnNonPermittedMethod() { - nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN); - nodeUsingJwtPublicKey.verify(net.netVersionUnauthorizedResponse()); + nodeUsingJwtPublicKey.useAuthenticationTokenInHeaderForJsonRpc(TOKEN_ALLOWING_NET_PEER_COUNT); + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorized()); + nodeUsingAuthFile.verify(net.netServicesUnauthorized()); + } + + @Test + public void jsonRpcMethodShouldFailWhenThereIsNoToken() { + nodeUsingJwtPublicKey.verify(net.netVersionUnauthorized()); + nodeUsingJwtPublicKey.verify(net.netServicesUnauthorized()); } @Test From 6eab2862ff365c0c2e3c8b61ba8507b9923705f2 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 15:49:59 +1000 Subject: [PATCH 34/38] compile error Signed-off-by: Jason Frame --- .../tests/acceptance/dsl/condition/net/ExpectUnauthorized.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java index b532d14dcf2..37a182bc4af 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/net/ExpectUnauthorized.java @@ -26,7 +26,7 @@ public class ExpectUnauthorized implements Condition { private static final String UNAUTHORIZED = "Unauthorized"; private final Transaction transaction; - public ExpectUnauthorized(final Transaction transaction) { + public ExpectUnauthorized(final Transaction transaction) { this.transaction = transaction; } From 80deef5b91383735db8a93c0a4e5bd6b5713ccdf Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 16:11:42 +1000 Subject: [PATCH 35/38] add jwt into the cli options Signed-off-by: Jason Frame --- .../java/org/hyperledger/besu/cli/StandaloneCommand.java | 8 ++++---- .../java/org/hyperledger/besu/cli/BesuCommandTest.java | 4 ++-- besu/src/test/resources/everything_config.toml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 8cf0e5eb1bc..59bc9b0084f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -66,10 +66,10 @@ class StandaloneCommand implements DefaultCommandValues { String rpcHttpAuthenticationCredentialsFile = null; @CommandLine.Option( - names = {"--rpc-http-authentication-public-key-file"}, + names = {"--rpc-http-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "External JSON-RPC HTTP authentication public key file in PEM format used for validating JWT", + "JWT Public key file for JSON-RPC HTTP authentication", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -82,10 +82,10 @@ class StandaloneCommand implements DefaultCommandValues { String rpcWsAuthenticationCredentialsFile = null; @CommandLine.Option( - names = {"--rpc-ws-authentication-public-key-file"}, + names = {"--rpc-ws-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "External JSON-RPC WebSocket authentication public key file in PEM format used for validating JWT", + "JWT Public key file for JSON-RPC WebSocket authentication", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 90925c4445b..06fb6c0d9b5 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -3026,7 +3026,7 @@ public void requiredBlocksMulpleBlocksTwoArgs() { @Test public void httpAuthenticationPublicKeyIsConfigured() throws IOException { final Path publicKey = Files.createTempFile("public_key", ""); - parseCommand("--rpc-http-authentication-public-key-file", publicKey.toString()); + parseCommand("--rpc-http-authentication-jwt-public-key-file", publicKey.toString()); verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); @@ -3049,7 +3049,7 @@ public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() { @Test public void wsAuthenticationPublicKeyIsConfigured() throws IOException { final Path publicKey = Files.createTempFile("public_key", ""); - parseCommand("--rpc-ws-authentication-public-key-file", publicKey.toString()); + parseCommand("--rpc-ws-authentication-jwt-public-key-file", publicKey.toString()); verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index fa88591dc04..1f26adb0022 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -50,7 +50,7 @@ rpc-http-apis=["DEBUG","ETH"] rpc-http-cors-origins=["none"] rpc-http-authentication-enabled=false rpc-http-authentication-credentials-file="none" -rpc-http-authentication-public-key-file="none" +rpc-http-authentication-jwt-public-key-file="none" # GRAPHQL HTTP graphql-http-enabled=false @@ -66,7 +66,7 @@ rpc-ws-host="9.10.11.12" rpc-ws-port=9101 rpc-ws-authentication-enabled=false rpc-ws-authentication-credentials-file="none" -rpc-ws-authentication-public-key-file="none" +rpc-ws-authentication-jwt-public-key-file="none" # Prometheus Metrics Endpoint metrics-enabled=false From f3aaf7da12b1f173f25ce127ddef57debec35025 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 16:13:11 +1000 Subject: [PATCH 36/38] remove unnecessary capitalisation in cli description Signed-off-by: Jason Frame --- .../main/java/org/hyperledger/besu/cli/StandaloneCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index 59bc9b0084f..d835d9f9f3f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -69,7 +69,7 @@ class StandaloneCommand implements DefaultCommandValues { names = {"--rpc-http-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "JWT Public key file for JSON-RPC HTTP authentication", + "JWT public key file for JSON-RPC HTTP authentication", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -85,7 +85,7 @@ class StandaloneCommand implements DefaultCommandValues { names = {"--rpc-ws-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "JWT Public key file for JSON-RPC WebSocket authentication", + "JWT public key file for JSON-RPC WebSocket authentication", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; From 19fedcf7b0dcf0e1f5b63edf98a178973162c570 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 16:20:18 +1000 Subject: [PATCH 37/38] spotless Signed-off-by: Jason Frame --- .../java/org/hyperledger/besu/cli/StandaloneCommand.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java index d835d9f9f3f..7990a8f79d1 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/StandaloneCommand.java @@ -68,8 +68,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-http-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "JWT public key file for JSON-RPC HTTP authentication", + description = "JWT public key file for JSON-RPC HTTP authentication", arity = "1") final File rpcHttpAuthenticationPublicKeyFile = null; @@ -84,8 +83,7 @@ class StandaloneCommand implements DefaultCommandValues { @CommandLine.Option( names = {"--rpc-ws-authentication-jwt-public-key-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "JWT public key file for JSON-RPC WebSocket authentication", + description = "JWT public key file for JSON-RPC WebSocket authentication", arity = "1") final File rpcWsAuthenticationPublicKeyFile = null; From 08fb77b6b4403ad607312b1c017061e412919da4 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 15 Nov 2019 16:52:47 +1000 Subject: [PATCH 38/38] update the cli options when using process runner in ATs Signed-off-by: Jason Frame --- .../besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index 4ae9121ee06..246766603ec 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -133,7 +133,7 @@ public void startNode(final BesuNode node) { params.add(node.jsonRpcConfiguration().getAuthenticationCredentialsFile()); } if (node.jsonRpcConfiguration().getAuthenticationPublicKeyFile() != null) { - params.add("--rpc-http-authentication-public-key-file"); + params.add("--rpc-http-authentication-jwt-public-key-file"); params.add(node.jsonRpcConfiguration().getAuthenticationPublicKeyFile().getAbsolutePath()); } } @@ -154,7 +154,7 @@ public void startNode(final BesuNode node) { params.add(node.webSocketConfiguration().getAuthenticationCredentialsFile()); } if (node.webSocketConfiguration().getAuthenticationPublicKeyFile() != null) { - params.add("--rpc-ws-authentication-public-key-file"); + params.add("--rpc-ws-authentication-jwt-public-key-file"); params.add( node.webSocketConfiguration().getAuthenticationPublicKeyFile().getAbsolutePath()); }