Privacy RPC multi-tenancy token public enclave key validation (#266)

Signed-off-by: Jason Frame <jasonwframe@gmail.com>
pull/269/head
Jason Frame 5 years ago committed by GitHub
parent 991dcf9237
commit c8ff44d035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 57
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecorator.java
  2. 27
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyUserUtil.java
  3. 16
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethods.java
  4. 94
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java
  5. 51
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyUserUtilTest.java
  6. 19
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java

@ -0,0 +1,57 @@
/*
* 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.internal.privacy.methods;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcUnauthorizedResponse;
import java.util.Optional;
import io.vertx.ext.auth.User;
import org.apache.logging.log4j.Logger;
public class MultiTenancyRpcMethodDecorator implements JsonRpcMethod {
private static final Logger LOG = getLogger();
private JsonRpcMethod rpcMethod;
public MultiTenancyRpcMethodDecorator(final JsonRpcMethod rpcMethod) {
this.rpcMethod = rpcMethod;
}
@Override
public String getName() {
return rpcMethod.getName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final Optional<User> user = requestContext.getUser();
final Object id = requestContext.getRequest().getId();
if (user.isEmpty()) {
LOG.error("Request does not contain an authorization token");
return new JsonRpcUnauthorizedResponse(id, JsonRpcError.UNAUTHORIZED);
} else if (MultiTenancyUserUtil.enclavePublicKey(user).isEmpty()) {
LOG.error("Request token does not contain an enclave public key");
return new JsonRpcUnauthorizedResponse(id, JsonRpcError.UNAUTHORIZED);
} else {
return rpcMethod.response(requestContext);
}
}
}

@ -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.ethereum.api.jsonrpc.internal.privacy.methods;
import java.util.Optional;
import io.vertx.ext.auth.User;
public class MultiTenancyUserUtil {
private static final String ENCLAVE_PRIVACY_PUBLIC_KEY_CLAIM = "privacyPublicKey";
public static Optional<String> enclavePublicKey(final Optional<User> user) {
return user.map(u -> u.principal().getString(ENCLAVE_PRIVACY_PUBLIC_KEY_CLAIM));
}
}

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.LatestNonceProvider;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.DisabledPrivacyRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyRpcMethodDecorator;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
@ -79,9 +80,7 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
return create(privacyController).entrySet().stream()
.collect(
Collectors.toMap(
Entry::getKey,
rpcMethod ->
createPrivacyMethod(privacyParameters.isEnabled(), rpcMethod.getValue())));
Entry::getKey, entry -> createPrivacyMethod(privacyParameters, entry.getValue())));
}
protected abstract Map<String, JsonRpcMethod> create(final PrivacyController privacyController);
@ -103,7 +102,14 @@ public abstract class PrivacyApiGroupJsonRpcMethods extends ApiGroupJsonRpcMetho
return new RandomSigningPrivateMarkerTransactionFactory(privateContractAddress);
}
private JsonRpcMethod createPrivacyMethod(final Boolean enabled, final JsonRpcMethod rpcMethod) {
return enabled ? rpcMethod : new DisabledPrivacyRpcMethod(rpcMethod.getName());
private JsonRpcMethod createPrivacyMethod(
final PrivacyParameters privacyParameters, final JsonRpcMethod rpcMethod) {
if (privacyParameters.isEnabled() && privacyParameters.isMultiTenancyEnabled()) {
return new MultiTenancyRpcMethodDecorator(rpcMethod);
} else if (!privacyParameters.isEnabled()) {
return new DisabledPrivacyRpcMethod(rpcMethod.getName());
} else {
return rpcMethod;
}
}
}

@ -0,0 +1,94 @@
/*
* 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.internal.privacy.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcUnauthorizedResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.jwt.impl.JWTUser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MultiTenancyRpcMethodDecoratorTest {
@Mock private JsonRpcMethod jsonRpcMethod;
private final JsonRpcRequest rpcRequest = new JsonRpcRequest("1", "test", new String[] {"a"});
@Test
public void delegatesWhenHasValidToken() {
final JsonObject principle = new JsonObject();
principle.put("privacyPublicKey", "ABC123");
final JWTUser user = new JWTUser(principle, "");
final JsonRpcRequestContext rpcRequestContext = new JsonRpcRequestContext(rpcRequest, user);
when(jsonRpcMethod.response(rpcRequestContext))
.thenReturn(new JsonRpcSuccessResponse("1", "b"));
when(jsonRpcMethod.getName()).thenReturn("delegate");
final MultiTenancyRpcMethodDecorator tokenRpcDecorator =
new MultiTenancyRpcMethodDecorator(jsonRpcMethod);
assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate");
final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext);
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isEqualTo("b");
}
@Test
public void failsWhenHasNoToken() {
final JsonRpcRequestContext rpcRequestContext = new JsonRpcRequestContext(rpcRequest);
final MultiTenancyRpcMethodDecorator tokenRpcDecorator =
new MultiTenancyRpcMethodDecorator(jsonRpcMethod);
when(jsonRpcMethod.getName()).thenReturn("delegate");
assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate");
final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext);
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.UNAUTHORIZED);
final JsonRpcUnauthorizedResponse errorResponse = (JsonRpcUnauthorizedResponse) response;
assertThat(errorResponse.getError()).isEqualTo(JsonRpcError.UNAUTHORIZED);
}
@Test
public void failsWhenTokenDoesNotHavePrivacyPublicKey() {
final JWTUser user = new JWTUser(new JsonObject(), "");
final JsonRpcRequestContext rpcRequestContext = new JsonRpcRequestContext(rpcRequest, user);
final MultiTenancyRpcMethodDecorator tokenRpcDecorator =
new MultiTenancyRpcMethodDecorator(jsonRpcMethod);
when(jsonRpcMethod.getName()).thenReturn("delegate");
assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate");
final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext);
assertThat(response.getType()).isEqualTo(JsonRpcResponseType.UNAUTHORIZED);
final JsonRpcUnauthorizedResponse errorResponse = (JsonRpcUnauthorizedResponse) response;
assertThat(errorResponse.getError()).isEqualTo(JsonRpcError.UNAUTHORIZED);
}
}

@ -0,0 +1,51 @@
/*
* 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.internal.privacy.methods;
import static java.util.Optional.empty;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyUserUtil.enclavePublicKey;
import java.util.Optional;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.jwt.impl.JWTUser;
import org.junit.Test;
public class MultiTenancyUserUtilTest {
@Test
public void noEnclavePublicKeyWhenNoUserProvided() {
assertThat(enclavePublicKey(empty())).isEmpty();
}
@Test
public void noEnclavePublicKeyWhenUserWithoutEnclavePublicKeyClaimProvided() {
final JsonObject token = new JsonObject();
final Optional<User> user = Optional.of(new JWTUser(token, ""));
assertThat(enclavePublicKey(user)).isEmpty();
}
@Test
public void enclavePublicKeyKeyReturnedForUserWithEnclavePublicKeyClaim() {
final JsonObject principle = new JsonObject();
principle.put("privacyPublicKey", "ABC123");
final Optional<User> user = Optional.of(new JWTUser(principle, ""));
assertThat(enclavePublicKey(user)).contains("ABC123");
}
}

@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.MultiTenancyRpcMethodDecorator;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType;
@ -56,6 +57,24 @@ public class PrivacyApiGroupJsonRpcMethodsTest {
privacyApiGroupJsonRpcMethods = createPrivacyApiGroupJsonRpcMethods();
}
@Test
public void rpcMethodsCreatedWhenMultiTenancyIsEnabledHaveMultiTenancyValidator() {
final Map<String, JsonRpcMethod> rpcMethods = privacyApiGroupJsonRpcMethods.create();
final JsonRpcMethod privMethod = rpcMethods.get("priv_method");
assertThat(privMethod).isNotSameAs(rpcMethod);
assertThat(privMethod.getClass()).hasSameClassAs(MultiTenancyRpcMethodDecorator.class);
}
@Test
public void rpcsCreatedWithoutMultiTenancyUseOriginalRpcMethod() {
when(privacyParameters.isEnabled()).thenReturn(true);
final Map<String, JsonRpcMethod> rpcMethods = privacyApiGroupJsonRpcMethods.create();
final JsonRpcMethod privMethod = rpcMethods.get("priv_method");
assertThat(privMethod).isSameAs(rpcMethod);
}
@Test
public void rpcMethodsCreatedWhenPrivacyIsNotEnabledAreDisabled() {
final Map<String, JsonRpcMethod> rpcMethods = privacyApiGroupJsonRpcMethods.create();

Loading…
Cancel
Save