Search in sources :

Example 11 with NodeUri

use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.

the class EnclaveKeySynchroniserImpl method syncKeys.

@Override
public void syncKeys() {
    NodeUri nodeUri = Optional.of(RuntimeContext.getInstance()).map(RuntimeContext::getP2pServerUri).map(NodeUri::create).get();
    List<ActiveNode> activeNodes = networkStore.getActiveNodes().filter(a -> a.getUri().equals(nodeUri)).collect(Collectors.toList());
    if (activeNodes.isEmpty()) {
        return;
    }
    final Set<PublicKey> storedKeys = activeNodes.stream().flatMap(a -> a.getKeys().stream()).collect(Collectors.toSet());
    final Set<PublicKey> keys = enclave.getPublicKeys();
    if (!storedKeys.equals(keys)) {
        final Set<PublicKey> allKeys = Stream.concat(storedKeys.stream(), keys.stream()).collect(Collectors.toUnmodifiableSet());
        activeNodes.forEach(activeNode -> {
            ActiveNode modified = ActiveNode.Builder.from(activeNode).withKeys(allKeys).build();
            networkStore.store(modified);
        });
    }
}
Also used : EnclaveKeySynchroniser(com.quorum.tessera.discovery.EnclaveKeySynchroniser) PublicKey(com.quorum.tessera.encryption.PublicKey) Set(java.util.Set) NodeUri(com.quorum.tessera.discovery.NodeUri) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) ActiveNode(com.quorum.tessera.discovery.ActiveNode) NetworkStore(com.quorum.tessera.discovery.NetworkStore) List(java.util.List) Stream(java.util.stream.Stream) RuntimeContext(com.quorum.tessera.context.RuntimeContext) Enclave(com.quorum.tessera.enclave.Enclave) Optional(java.util.Optional) PublicKey(com.quorum.tessera.encryption.PublicKey) NodeUri(com.quorum.tessera.discovery.NodeUri) ActiveNode(com.quorum.tessera.discovery.ActiveNode) RuntimeContext(com.quorum.tessera.context.RuntimeContext)

Example 12 with NodeUri

use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.

the class P2PRestApp method getSingletons.

@Override
public Set<Object> getSingletons() {
    RuntimeContext runtimeContext = RuntimeContext.getInstance();
    List<URI> peers = runtimeContext.getPeers();
    LOGGER.debug("Found configured peers {}", peers);
    peers.stream().map(NodeUri::create).map(NodeUri::asURI).peek(u -> LOGGER.debug("Adding {} to party store", u)).forEach(partyStore::store);
    final PartyInfoResource partyInfoResource = new PartyInfoResource(discovery, partyInfoParser, runtimeContext.getP2pClient(), enclave, runtimeContext.isRemoteKeyValidation());
    final IPWhitelistFilter iPWhitelistFilter = new IPWhitelistFilter();
    final TransactionResource transactionResource = new TransactionResource(transactionManager, batchResendManager, legacyResendManager);
    final UpCheckResource upCheckResource = new UpCheckResource();
    final PrivacyGroupResource privacyGroupResource = new PrivacyGroupResource(privacyGroupManager);
    if (runtimeContext.isRecoveryMode()) {
        final RecoveryResource recoveryResource = new RecoveryResource(transactionManager, batchResendManager);
        return Set.of(partyInfoResource, iPWhitelistFilter, recoveryResource, upCheckResource);
    }
    return Set.of(partyInfoResource, iPWhitelistFilter, transactionResource, privacyGroupResource, upCheckResource);
}
Also used : PartyInfoParser(com.quorum.tessera.p2p.partyinfo.PartyInfoParser) AppType(com.quorum.tessera.config.AppType) Logger(org.slf4j.Logger) TransactionManager(com.quorum.tessera.transaction.TransactionManager) LoggerFactory(org.slf4j.LoggerFactory) BatchResendManager(com.quorum.tessera.recovery.workflow.BatchResendManager) Set(java.util.Set) PrivacyGroupManager(com.quorum.tessera.privacygroup.PrivacyGroupManager) NodeUri(com.quorum.tessera.discovery.NodeUri) PartyStore(com.quorum.tessera.p2p.partyinfo.PartyStore) Objects(java.util.Objects) Discovery(com.quorum.tessera.discovery.Discovery) ApplicationPath(jakarta.ws.rs.ApplicationPath) List(java.util.List) Stream(java.util.stream.Stream) UpCheckResource(com.quorum.tessera.api.common.UpCheckResource) GlobalFilter(com.quorum.tessera.api.filter.GlobalFilter) RuntimeContext(com.quorum.tessera.context.RuntimeContext) Enclave(com.quorum.tessera.enclave.Enclave) URI(java.net.URI) LegacyResendManager(com.quorum.tessera.recovery.workflow.LegacyResendManager) Collectors.toSet(java.util.stream.Collectors.toSet) IPWhitelistFilter(com.quorum.tessera.api.filter.IPWhitelistFilter) TesseraRestApplication(com.quorum.tessera.app.TesseraRestApplication) UpCheckResource(com.quorum.tessera.api.common.UpCheckResource) IPWhitelistFilter(com.quorum.tessera.api.filter.IPWhitelistFilter) NodeUri(com.quorum.tessera.discovery.NodeUri) RuntimeContext(com.quorum.tessera.context.RuntimeContext) URI(java.net.URI)

Example 13 with NodeUri

use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.

the class PartyInfoResource method partyInfo.

/**
 * Update the local partyinfo store with the encoded partyinfo included in the request.
 *
 * @param payload The encoded partyinfo information pushed by the caller
 * @return an empty 200 OK Response if the local node is using remote key validation; a 200 OK
 *     Response wrapping an encoded partyinfo that contains only the local node's URL if not using
 *     remote key validation; a 500 Internal Server Error if remote key validation fails
 */
@Operation(summary = "/partyinfo", operationId = "broadcastPartyInfo", description = "broadcast partyinfo information to server")
@ApiResponse(responseCode = "200", description = "server successfully updated its party info", content = @Content(array = @ArraySchema(schema = @Schema(description = "empty if server is using remote key validation, else is encoded partyinfo object containing only the server's URL", type = "string", format = "byte"))))
@ApiResponse(responseCode = "500", description = "Validation failed (if server is using remote key validation)")
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response partyInfo(@RequestBody(required = true, description = "partyinfo object") final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) @Parameter(description = "client's supported API versions", array = @ArraySchema(schema = @Schema(type = "string"))) final List<String> headers) {
    final PartyInfo partyInfo = partyInfoParser.from(payload);
    final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream().filter(Objects::nonNull).flatMap(v -> Arrays.stream(v.split(","))).collect(Collectors.toSet());
    final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions);
    LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl());
    if (!enableKeyValidation) {
        LOGGER.debug("Key validation not enabled, passing PartyInfo through");
        discovery.onUpdate(nodeInfo);
        partyInfo.getParties().stream().map(Party::getUrl).map(NodeUri::create).map(NodeUri::asURI).forEach(partyStore::store);
        // create an empty party info object with our URL to send back
        // this is used by older versions (before 0.10.0), but we don't want to give any info back
        final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet());
        final byte[] returnData = partyInfoParser.to(emptyInfo);
        return Response.ok(returnData).build();
    }
    final PublicKey localPublicKey = enclave.defaultPublicKey();
    final Predicate<Recipient> isValidRecipient = r -> {
        try {
            LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl());
            final String dataToEncrypt = UUID.randomUUID().toString();
            final EncodedPayload encodedPayload = enclave.encryptPayload(dataToEncrypt.getBytes(), localPublicKey, List.of(r.getKey()), PrivacyMetadata.Builder.forStandardPrivate().build());
            final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload);
            try (Response response = restClient.target(r.getUrl()).path("partyinfo").path("validate").request().post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) {
                LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl());
                final String responseData = response.readEntity(String.class);
                final boolean isValid = Objects.equals(responseData, dataToEncrypt);
                if (!isValid) {
                    LOGGER.warn("Validation of key {} for peer {} failed.  Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl());
                    LOGGER.debug("Response from {} was {}", r.getUrl(), responseData);
                }
                return isValid;
            }
        // Assume any and all exceptions to mean invalid. enclave bubbles up nacl array out of
        // bounds when calculating shared key from invalid data
        } catch (Exception ex) {
            LOGGER.debug(null, ex);
            return false;
        }
    };
    final String partyInfoSender = partyInfo.getUrl();
    final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender));
    // Validate caller and treat no valid certs as security issue.
    final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream().filter(isSender.and(isValidRecipient)).map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())).collect(Collectors.toSet());
    LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys);
    if (validatedSendersKeys.isEmpty()) {
        throw new SecurityException("No validated keys found for peer " + partyInfoSender);
    }
    // End validation stuff
    final NodeInfo reducedNodeInfo = NodeInfo.Builder.create().withUrl(partyInfoSender).withSupportedApiVersions(versions).withRecipients(validatedSendersKeys).build();
    discovery.onUpdate(reducedNodeInfo);
    partyInfo.getParties().stream().map(Party::getUrl).map(NodeUri::create).map(NodeUri::asURI).forEach(partyStore::store);
    return Response.ok().build();
}
Also used : PublicKey(com.quorum.tessera.encryption.PublicKey) java.util(java.util) NodeInfoUtil(com.quorum.tessera.partyinfo.model.NodeInfoUtil) LoggerFactory(org.slf4j.LoggerFactory) Party(com.quorum.tessera.partyinfo.model.Party) GetPartyInfoResponse(com.quorum.tessera.p2p.model.GetPartyInfoResponse) Content(io.swagger.v3.oas.annotations.media.Content) Discovery(com.quorum.tessera.discovery.Discovery) Operation(io.swagger.v3.oas.annotations.Operation) Response(jakarta.ws.rs.core.Response) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Objects.requireNonNull(java.util.Objects.requireNonNull) RequestBody(io.swagger.v3.oas.annotations.parameters.RequestBody) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse) Constants(com.quorum.tessera.shared.Constants) Schema(io.swagger.v3.oas.annotations.media.Schema) PartyInfoParser(com.quorum.tessera.p2p.partyinfo.PartyInfoParser) Client(jakarta.ws.rs.client.Client) Logger(org.slf4j.Logger) Collections.emptySet(java.util.Collections.emptySet) Collections.emptyList(java.util.Collections.emptyList) Predicate(java.util.function.Predicate) com.quorum.tessera.enclave(com.quorum.tessera.enclave) JsonArrayBuilder(jakarta.json.JsonArrayBuilder) NodeUri(com.quorum.tessera.discovery.NodeUri) jakarta.ws.rs(jakarta.ws.rs) Collectors(java.util.stream.Collectors) PartyStore(com.quorum.tessera.p2p.partyinfo.PartyStore) Json(jakarta.json.Json) Entity(jakarta.ws.rs.client.Entity) Parameter(io.swagger.v3.oas.annotations.Parameter) ArraySchema(io.swagger.v3.oas.annotations.media.ArraySchema) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) MediaType(jakarta.ws.rs.core.MediaType) Tag(io.swagger.v3.oas.annotations.tags.Tag) Recipient(com.quorum.tessera.partyinfo.model.Recipient) PublicKey(com.quorum.tessera.encryption.PublicKey) NodeUri(com.quorum.tessera.discovery.NodeUri) Recipient(com.quorum.tessera.partyinfo.model.Recipient) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) GetPartyInfoResponse(com.quorum.tessera.p2p.model.GetPartyInfoResponse) Response(jakarta.ws.rs.core.Response) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Operation(io.swagger.v3.oas.annotations.Operation) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse)

Example 14 with NodeUri

use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.

the class PartyInfoBroadcaster method run.

/**
 * Iterates over all known parties and contacts them for the current state of their known node
 * discovery list
 *
 * <p>For Tessera 0.9 backwards, after contacting the known parties, this poller then updates this
 * nodes list of data with any new information collected.
 *
 * <p>This behaviour is now deprecated since the /partyinfo API call now has been made more strict
 * with node validation to prevent exploiting the API to attack the Tessera network.
 *
 * <p>This call is merely to let its parties know about this node existence, any recipients that
 * want to be added to this node's PartyInfo will need to make their own partyinfo call and
 * validation
 */
@Override
public void run() {
    LOGGER.info("Started PartyInfo polling round");
    partyStore.loadFromConfigIfEmpty();
    final NodeInfo nodeInfo = discovery.getCurrent();
    final NodeUri ourUrl = NodeUri.create(nodeInfo.getUrl());
    final PartyInfo partyInfo = PartyInfoBuilder.create().withUri(nodeInfo.getUrl()).withRecipients(nodeInfo.getRecipientsAsMap()).build();
    final byte[] encodedPartyInfo = partyInfoParser.to(partyInfo);
    LOGGER.debug("Contacting following peers with PartyInfo: {}", partyInfo.getParties());
    LOGGER.debug("Sending party info {}", nodeInfo);
    partyStore.getParties().stream().map(NodeUri::create).filter(url -> !ourUrl.equals(url)).forEach(url -> pollSingleParty(url.asString(), encodedPartyInfo));
    LOGGER.info("Finished PartyInfo polling round");
}
Also used : Logger(org.slf4j.Logger) Executor(java.util.concurrent.Executor) LoggerFactory(org.slf4j.LoggerFactory) CompletableFuture(java.util.concurrent.CompletableFuture) NodeUri(com.quorum.tessera.discovery.NodeUri) Executors(java.util.concurrent.Executors) Objects(java.util.Objects) Discovery(com.quorum.tessera.discovery.Discovery) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) ProcessingException(jakarta.ws.rs.ProcessingException) Optional(java.util.Optional) P2pClient(com.quorum.tessera.partyinfo.P2pClient) URI(java.net.URI) PartyInfoBuilder(com.quorum.tessera.partyinfo.model.PartyInfoBuilder) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) NodeUri(com.quorum.tessera.discovery.NodeUri) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo)

Aggregations

NodeUri (com.quorum.tessera.discovery.NodeUri)14 RuntimeContext (com.quorum.tessera.context.RuntimeContext)7 ActiveNode (com.quorum.tessera.discovery.ActiveNode)7 PublicKey (com.quorum.tessera.encryption.PublicKey)7 URI (java.net.URI)7 Discovery (com.quorum.tessera.discovery.Discovery)4 Test (org.junit.Test)4 Logger (org.slf4j.Logger)4 LoggerFactory (org.slf4j.LoggerFactory)4 NetworkStore (com.quorum.tessera.discovery.NetworkStore)3 Enclave (com.quorum.tessera.enclave.Enclave)3 NodeInfo (com.quorum.tessera.partyinfo.node.NodeInfo)3 List (java.util.List)3 Objects (java.util.Objects)3 Optional (java.util.Optional)3 Set (java.util.Set)3 Collectors (java.util.stream.Collectors)3 PartyInfoParser (com.quorum.tessera.p2p.partyinfo.PartyInfoParser)2 PartyStore (com.quorum.tessera.p2p.partyinfo.PartyStore)2 PartyInfo (com.quorum.tessera.partyinfo.model.PartyInfo)2