Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[NC-2044] Added the authentication service to the websocket service #829

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
private JsonRequestFactories jsonRequestFactories;
private HttpRequestFactory httpRequestFactory;
private Optional<EthNetworkConfig> ethNetworkConfig = Optional.empty();
private boolean useWsForJsonRpc = false;

public PantheonNode(
final String name,
Expand Down Expand Up @@ -158,6 +159,18 @@ private Optional<String> wsRpcBaseUrl() {
}
}

private Optional<String> wsRpcBaseHttpUrl() {
if (isWebSocketsRpcEnabled()) {
return Optional.of(
"http://"
+ webSocketConfiguration.getHost()
+ ":"
+ portsProperties.getProperty("ws-rpc"));
} else {
return Optional.empty();
}
}

@Override
public Optional<Integer> jsonRpcWebSocketPort() {
if (isWebSocketsRpcEnabled()) {
Expand All @@ -174,10 +187,19 @@ public String hostName() {

private JsonRequestFactories jsonRequestFactories() {
if (jsonRequestFactories == null) {
final Optional<String> baseUrl;
final String port;
if (useWsForJsonRpc) {
baseUrl = wsRpcBaseUrl();
port = "8546";
} else {
baseUrl = jsonRpcBaseUrl();
port = "8545";
}
final Web3jService web3jService =
jsonRpcBaseUrl()
baseUrl
.map(url -> new HttpService(url))
.orElse(new HttpService("http://" + LOCALHOST + ":8545"));
.orElse(new HttpService("http://" + LOCALHOST + ":" + port));

jsonRequestFactories =
new JsonRequestFactories(
Expand All @@ -193,8 +215,17 @@ private JsonRequestFactories jsonRequestFactories() {

private HttpRequestFactory httpRequestFactory() {
if (httpRequestFactory == null) {
final Optional<String> baseUrl;
final String port;
if (useWsForJsonRpc) {
baseUrl = wsRpcBaseHttpUrl();
port = "8546";
} else {
baseUrl = jsonRpcBaseUrl();
port = "8545";
}
httpRequestFactory =
new HttpRequestFactory(jsonRpcBaseUrl().orElse("http://" + LOCALHOST + ":8545"));
new HttpRequestFactory(baseUrl.orElse("http://" + LOCALHOST + ":" + port));
}
return httpRequestFactory;
}
Expand All @@ -216,6 +247,12 @@ public void useWebSocketsForJsonRpc() {
if (jsonRequestFactories != null) {
jsonRequestFactories.shutdown();
}

if (httpRequestFactory != null) {
httpRequestFactory = null;
}

useWsForJsonRpc = true;
}

private void checkIfWebSocketEndpointIsAvailable(final String url) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ public void startNode(final PantheonNode node) {
params.add(String.join(",", node.bootnodes().toString()));

if (node.jsonRpcEnabled()) {
params.add("--rpc-enabled");
params.add("--rpc-http-enabled");
params.add("--rpc-listen");
params.add(node.jsonRpcListenAddress().get());
params.add("--rpc-api");
params.add("--rpc-http-api");
params.add(apiList(node.jsonRpcConfiguration().getRpcApis()));
if (node.jsonRpcConfiguration().isAuthenticationEnabled()) {
params.add("--rpc-http-authentication-enabled");
Expand All @@ -90,11 +90,18 @@ public void startNode(final PantheonNode node) {
}

if (node.wsRpcEnabled()) {
params.add("--ws-enabled");
params.add("--rpc-ws-enabled");
params.add("--ws-listen");
params.add(node.wsRpcListenAddress().get());
params.add("--ws-api");
params.add("--rpc-ws-api");
params.add(apiList(node.webSocketConfiguration().getRpcApis()));
if (node.webSocketConfiguration().isAuthenticationEnabled()) {
params.add("--rpc-ws-authentication-enabled");
}
if (node.webSocketConfiguration().getAuthenticationCredentialsFile() != null) {
params.add("--rpc-ws-authentication-credentials-file");
params.add(node.webSocketConfiguration().getAuthenticationCredentialsFile());
}
}

if (node.ethNetworkConfig().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ public PantheonFactoryConfigurationBuilder webSocketEnabled() {
return this;
}

public PantheonFactoryConfigurationBuilder webSocketAuthenticationEnabled()
throws URISyntaxException {
final String authTomlPath =
Paths.get(ClassLoader.getSystemResource("authentication/auth.toml").toURI())
.toAbsolutePath()
.toString();

this.webSocketConfiguration.setAuthenticationEnabled(true);
this.webSocketConfiguration.setAuthenticationCredentialsFile(authTomlPath);

return this;
}

public PantheonFactoryConfigurationBuilder setPermissioningConfiguration(
final PermissioningConfiguration permissioningConfiguration) {
this.permissioningConfiguration = Optional.of(permissioningConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ public PantheonNode createArchiveNodeWithAuthentication(final String name)
.build());
}

public PantheonNode createArchiveNodeWithAuthenticationOverWebSocket(final String name)
throws IOException, URISyntaxException {
return create(
new PantheonFactoryConfigurationBuilder()
.setName(name)
.webSocketEnabled()
.webSocketAuthenticationEnabled()
.build());
}

public PantheonNode createNodeWithP2pDisabled(final String name) throws IOException {
return create(
new PantheonFactoryConfigurationBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.tests.acceptance.jsonrpc;

import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;

import java.io.IOException;
import java.net.URISyntaxException;

import org.junit.Before;
import org.junit.Test;

public class WebsocketServiceLoginAcceptanceTest extends AcceptanceTestBase {
private PantheonNode node;

@Before
public void setUp() throws IOException, URISyntaxException {
node = pantheon.createArchiveNodeWithAuthenticationOverWebSocket("node1");
cluster.start(node);
node.useWebSocketsForJsonRpc();
}

@Test
public void shouldFailLoginWithWrongCredentials() {
node.verify(login.loginFails("user", "badpassword"));
}

@Test
public void shouldSucceedLoginWithCorrectCredentials() {
node.verify(login.loginSucceeds("user", "pegasys"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
package tech.pegasys.pantheon.ethereum.jsonrpc.authentication;

import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Optional;
import javax.annotation.Nullable;

import com.google.common.annotations.VisibleForTesting;
import io.netty.handler.codec.http.HttpResponseStatus;
Expand Down Expand Up @@ -60,12 +62,16 @@ private AuthenticationService(
*/
public static Optional<AuthenticationService> create(
final Vertx vertx, final JsonRpcConfiguration config) {
final Optional<JWTAuthOptions> jwtAuthOptions = makeJwtAuthOptions(config);
final Optional<JWTAuthOptions> jwtAuthOptions =
makeJwtAuthOptions(
config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile());
if (!jwtAuthOptions.isPresent()) {
return Optional.empty();
}

final Optional<AuthProvider> credentialAuthProvider = makeCredentialAuthProvider(vertx, config);
final Optional<AuthProvider> credentialAuthProvider =
makeCredentialAuthProvider(
vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile());
if (!credentialAuthProvider.isPresent()) {
return Optional.empty();
}
Expand All @@ -77,8 +83,42 @@ public static Optional<AuthenticationService> create(
credentialAuthProvider.get()));
}

private static Optional<JWTAuthOptions> makeJwtAuthOptions(final JsonRpcConfiguration config) {
if (config.isAuthenticationEnabled() && config.getAuthenticationCredentialsFile() != null) {
/**
* Creates a ready for use set of authentication providers if authentication is configured to be
* on
*
* @param vertx The vertx instance that will be providing requests that this set of authentication
* providers will be handling
* @param config The {{@link JsonRpcConfiguration}} that describes this rpc setup
* @return Optionally an authentication service. If empty then authentication isn't to be enabled
* on this service
*/
public static Optional<AuthenticationService> create(
final Vertx vertx, final WebSocketConfiguration config) {
final Optional<JWTAuthOptions> jwtAuthOptions =
makeJwtAuthOptions(
config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile());
if (!jwtAuthOptions.isPresent()) {
return Optional.empty();
}

final Optional<AuthProvider> credentialAuthProvider =
makeCredentialAuthProvider(
vertx, config.isAuthenticationEnabled(), config.getAuthenticationCredentialsFile());
if (!credentialAuthProvider.isPresent()) {
return Optional.empty();
}

return Optional.of(
new AuthenticationService(
jwtAuthOptions.map(o -> JWTAuth.create(vertx, o)).get(),
jwtAuthOptions.get(),
credentialAuthProvider.get()));
}

private static Optional<JWTAuthOptions> makeJwtAuthOptions(
final boolean authenticationEnabled, @Nullable final String authenticationCredentialsFile) {
if (authenticationEnabled && authenticationCredentialsFile != null) {
final KeyPairGenerator keyGenerator;
try {
keyGenerator = KeyPairGenerator.getInstance("RSA");
Expand Down Expand Up @@ -107,12 +147,12 @@ private static Optional<JWTAuthOptions> makeJwtAuthOptions(final JsonRpcConfigur
}

private static Optional<AuthProvider> makeCredentialAuthProvider(
final Vertx vertx, final JsonRpcConfiguration config) {
if (config.isAuthenticationEnabled() && config.getAuthenticationCredentialsFile() != null) {
final Vertx vertx,
final boolean authenticationEnabled,
@Nullable final String authenticationCredentialsFile) {
if (authenticationEnabled && authenticationCredentialsFile != null) {
return Optional.of(
new TomlAuthOptions()
.setTomlPath(config.getAuthenticationCredentialsFile())
.createProvider(vertx));
new TomlAuthOptions().setTomlPath(authenticationCredentialsFile).createProvider(vertx));
} else {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class WebSocketConfiguration {
private String host;
private Collection<RpcApi> rpcApis;
private long refreshDelay;
private boolean authenticationEnabled = false;
private String authenticationCredentialsFile;

public static WebSocketConfiguration createDefault() {
final WebSocketConfiguration config = new WebSocketConfiguration();
Expand Down Expand Up @@ -92,6 +94,8 @@ public String toString() {
.add("port", port)
.add("host", host)
.add("rpcApis", rpcApis)
.add("authenticationEnabled", authenticationEnabled)
.add("authenticationCredentialsFile", authenticationCredentialsFile)
.toString();
}

Expand Down Expand Up @@ -122,4 +126,20 @@ public void setRefreshDelay(final long refreshDelay) {
public long getRefreshDelay() {
return refreshDelay;
}

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;
}
}
Loading