Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORE-15939: NOT FOR REVIEW - Add support for Rekey and Rewrap bus processors, add REST APIs #4578

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
704b3c6
CORE-16242 - Synchronous RPC Pattern API Implementation for Crypto Wo…
thiagoviana Oct 16, 2023
7465279
CORE-17822 Apply prefix to topic overrides (#4893)
davidcurrie Oct 17, 2023
cc61a52
CORE-17045 - Membership platform upgrade test (#4817)
nikinagy Oct 17, 2023
017e82b
Merge pull request #4897 from corda/driessamyn/merge-perf-int-work-5
driessamyn Oct 17, 2023
2a6037a
CORE-17627: Add metadata to flow mapper states for mapper status (#4886)
JamesHR3 Oct 17, 2023
041a355
CORE-17388: Add session timeout metadata to store alongside the check…
JamesHR3 Oct 17, 2023
08daa23
CORE-17863: Close db connection pool after getOwnedKeyRecord (#4904)
yift-r3 Oct 17, 2023
40516d8
CORE-17867: Some extra logging for External Messaging (#4906)
vkolomeyko Oct 17, 2023
034c4fa
CORE-17882: Add a route for flow events (#4915)
JamesHR3 Oct 18, 2023
ffc0f34
Disable test.
driessamyn Oct 18, 2023
2adfca3
Merge pull request #4905 from corda/driessamyn/merge-perf-int-work-6
driessamyn Oct 18, 2023
0d868f2
CORE-16181 Implementing RPC client, routing external events through R…
ben-millar Oct 17, 2023
82df696
CORE-17626: Add integration test for flow mapper cleanup (#4907)
JamesHR3 Oct 18, 2023
aaf7973
Create Remove Stale Branches GitHub Action (#4909)
jacob-scott3141 Oct 18, 2023
365d145
Merge pull request #4923 from corda/driessamyn/merge-perf-int-work-7
driessamyn Oct 18, 2023
bed356e
CORE-17843 Flaky test FlowMapperServiceIntegrationTest.testStartRPCDu…
mbrkic-r3 Oct 18, 2023
cb329ab
CORE-17885: Use explicit conversion of session timeout to long (#4927)
JamesHR3 Oct 19, 2023
57a2d1f
First draft of the CryptoRewrapBusProcessor
anien Aug 3, 2023
7306d4a
do a rewrap
dickon Aug 4, 2023
88607ee
test rewrapWrappingKey got called
dickon Aug 4, 2023
9cf3cf7
create subscription and processor for rewrapping
dickon Aug 4, 2023
4c39ea8
Fix typo in the topic var
anien Aug 7, 2023
493c792
Get resource for rewrap subscription
anien Aug 7, 2023
efbec3e
Fix compilation issues - implement missing members
anien Aug 8, 2023
53bf9ed
Add comments
anien Aug 9, 2023
5f13061
Add first version of the REST API calls for the key rotation and tue …
anien Aug 10, 2023
48d7fac
Return empty list instead of events for now
anien Sep 4, 2023
3af9ccb
Update avro schemas names to align with API changes
anien Sep 4, 2023
71c7e5e
Add new lines
anien Sep 4, 2023
70fa8cd
Update kafka topic names
anien Sep 4, 2023
9656bf8
Align with API changes for individual key rotation request
anien Sep 4, 2023
c9f2891
Rename rotation to keyRotation
anien Sep 18, 2023
faba975
Add parameters to REST API for key rotation
anien Sep 18, 2023
1d085cc
Add param in the kdoc
anien Sep 21, 2023
9c682bc
Add publishing Kafka message to rest impl and add initialise method f…
anien Sep 21, 2023
d2a9fd7
Find the keys that need to be rewrapped
anien Sep 21, 2023
800fbb3
Add start and end timestamp for key rotation response, return respons…
anien Sep 25, 2023
dae40d1
Rename cryptoConfig to messagingConfig as that is what we use
anien Sep 26, 2023
70f17b9
Update timestamps according changes in api repo - status now contains…
anien Sep 26, 2023
7171027
Limit the number of key rotations if requested by user
anien Sep 26, 2023
a04885a
Add key rotation rest resource test plus do changes to key rotatino r…
anien Oct 2, 2023
2a9d8b7
Add oldKeyAlias and newKeyAlias validation to the key rotation rest r…
anien Oct 3, 2023
b9bf3ad
Simplify initialisation step in key rotation rest resource
anien Oct 3, 2023
7c873b5
Update kdocs - add throws, update key rotation response with correct …
anien Oct 3, 2023
fd09baf
Add comment
anien Oct 4, 2023
34180b8
Add first CryptoRekeyBusProcessor tests
anien Oct 4, 2023
8c4c2f0
Simplify tests by creating a getRecord function
anien Oct 4, 2023
d2388de
Add comment in the wrapping repo test reagarding the alias
anien Oct 4, 2023
1654376
Add test for zero limit in key rotation request
anien Oct 4, 2023
8b20744
Fix the way we retrieve tenantId, close wrapping repo, close publisher
anien Oct 5, 2023
0e1ac0a
Add first draft of the test that checks which tenant's wrapping repo …
anien Oct 5, 2023
44c32b3
Create functions for duplicate code in a test
anien Oct 9, 2023
b39a8bc
Remove crypto config from the test as it is not needed
anien Oct 9, 2023
4ed699c
Update topic name for key rotation
anien Oct 9, 2023
3f51051
bump API version to alpha version
dickon Oct 18, 2023
fa4dcf0
remove check code since crypto is not available
dickon Oct 19, 2023
c837520
remove unused imports (to fix detekt)
dickon Oct 19, 2023
ee6de08
remove test cases that cover client side config checks we can't do
dickon Oct 19, 2023
640750e
remove unused imports
dickon Oct 19, 2023
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
19 changes: 19 additions & 0 deletions .github/workflows/remove-stale-branches.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: 'Remove stale branches'
on:
schedule:
- cron: '0 0 * * 1-5'

jobs:
remove-stale-branches:
name: Remove stale branches
runs-on: ubuntu-latest
steps:
- uses: fpicalausa/[email protected]
with:
dry-run: true
days-before-branch-stale: 30
days-before-branch-delete: 14
stale-branch-message: "@{author} The branch [{branchName}]({branchUrl}) hasn't been updated in the last 30 days and is marked as stale. It will be removed in 14 days.\r\nIf you want to keep this branch around, delete this comment or add new commits to this branch."
exempt-protected-branches: true
exempt-branches-regex: "^(release\\/|feature\\/|poc\\/).*"
operations-per-run: 30
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package net.corda.applications.workers.smoketest.services

import net.corda.applications.workers.smoketest.utils.PLATFORM_VERSION
import net.corda.crypto.core.SecureHashImpl
import net.corda.crypto.core.toAvro
import net.corda.data.KeyValuePair
import net.corda.data.KeyValuePairList
import net.corda.data.crypto.SecureHashes
import net.corda.data.crypto.wire.CryptoRequestContext
import net.corda.data.crypto.wire.CryptoResponseContext
import net.corda.data.crypto.wire.ops.flow.FlowOpsRequest
import net.corda.data.crypto.wire.ops.flow.FlowOpsResponse
import net.corda.data.crypto.wire.ops.flow.queries.ByIdsFlowQuery
import net.corda.data.flow.event.FlowEvent
import net.corda.data.flow.event.external.ExternalEventContext
import net.corda.data.flow.event.external.ExternalEventResponse
import net.corda.e2etest.utilities.DEFAULT_CLUSTER
import net.corda.e2etest.utilities.conditionallyUploadCordaPackage
import net.corda.e2etest.utilities.conditionallyUploadCpiSigningCertificate
import net.corda.e2etest.utilities.getHoldingIdShortHash
import net.corda.e2etest.utilities.getOrCreateVirtualNodeFor
import net.corda.e2etest.utilities.registerStaticMember
import net.corda.messagebus.kafka.serialization.CordaAvroSerializationFactoryImpl
import net.corda.schema.registry.impl.AvroSchemaRegistryImpl
import net.corda.test.util.time.AutoTickTestClock
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.slf4j.LoggerFactory
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
import java.time.Instant
import java.util.UUID

/**
* Tests for the Crypto RPC service
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CryptoRPCSmokeTests {
private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build()
private val serializationFactory = CordaAvroSerializationFactoryImpl(
AvroSchemaRegistryImpl()
)

private val avroSerializer = serializationFactory.createAvroSerializer<FlowOpsRequest> { }
private val avroFlowEventDeserializer = serializationFactory.createAvroDeserializer({}, FlowEvent::class.java)
private val avroCryptoDeserializer = serializationFactory.createAvroDeserializer({}, FlowOpsResponse::class.java)

companion object {
const val TEST_CPI_NAME = "ledger-utxo-demo-app"
const val TEST_CPB_LOCATION = "/META-INF/ledger-utxo-demo-app.cpb"

val logger = LoggerFactory.getLogger(this::class.java.enclosingClass)
}

private val testRunUniqueId = UUID.randomUUID()
private val requestId = UUID.randomUUID()
private val flowId = UUID.randomUUID()
private val groupId = UUID.randomUUID().toString()
private val cpiName = "${TEST_CPI_NAME}_$testRunUniqueId"

private val aliceX500 = "CN=Alice-${testRunUniqueId}, OU=Application, O=R3, L=London, C=GB"

private val aliceHoldingId: String = getHoldingIdShortHash(aliceX500, groupId)

private val externalEventContext: ExternalEventContext = createExternalEventContext()
private lateinit var cryptoRequestContext: CryptoRequestContext

private fun createExternalEventContext(): ExternalEventContext {
val simpleContext = KeyValuePairList(
listOf(
KeyValuePair("Hello", "World!")
)
)

return ExternalEventContext.newBuilder()
.setContextProperties(simpleContext)
.setRequestId(requestId.toString())
.setFlowId(flowId.toString())
.build()
}

private val staticMemberList = listOf(
aliceX500
)

@BeforeAll
fun beforeAll() {
DEFAULT_CLUSTER.conditionallyUploadCpiSigningCertificate()

conditionallyUploadCordaPackage(
cpiName,
TEST_CPB_LOCATION,
groupId,
staticMemberList
)
val aliceActualHoldingId = getOrCreateVirtualNodeFor(aliceX500, cpiName)
assertThat(aliceActualHoldingId).isEqualTo(aliceHoldingId)
registerStaticMember(aliceHoldingId)
}

@BeforeEach
fun setup() {
cryptoRequestContext = createRequestContext()
}

@Test
fun `RPC endpoint accepts a request and returns back a response`() {
val url = "${System.getProperty("cryptoWorkerUrl")}api/$PLATFORM_VERSION/crypto"

logger.info("crypto url: $url")
val serializedPayload = avroSerializer.serialize(generateByIdsFlowOpsRequest())

val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers("Content-Type", "application/octet-stream")
.POST(HttpRequest.BodyPublishers.ofByteArray(serializedPayload))
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray())

assertThat(response.statusCode()).isEqualTo(200).withFailMessage("status code on response: ${response.statusCode()} url: $url")

val responseBody: ByteArray = response.body()
val responseEvent = avroFlowEventDeserializer.deserialize(responseBody)

assertThat(responseEvent).isNotNull

val deserializedExternalEventResponse = avroCryptoDeserializer.deserialize((responseEvent?.payload as ExternalEventResponse).payload.array())

assertThat(deserializedExternalEventResponse).isNotNull
assertStandardSuccessResponse(deserializedExternalEventResponse!!, testClock)
assertResponseContext(cryptoRequestContext, deserializedExternalEventResponse.context)
}

@Test
fun `RPC endpoint accepts a request and returns back an error response with 200 status`() {
val url = "${System.getProperty("cryptoWorkerUrl")}api/$PLATFORM_VERSION/crypto"

logger.info("crypto url: $url")
val serializedPayload = avroSerializer.serialize(generateByIdsFlowOpsRequest(returnError = true))

val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers("Content-Type", "application/octet-stream")
.POST(HttpRequest.BodyPublishers.ofByteArray(serializedPayload))
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray())

assertThat(response.statusCode()).isEqualTo(200).withFailMessage("status code on response: ${response.statusCode()} url: $url")

val responseBody: ByteArray = response.body()
val responseEvent = avroFlowEventDeserializer.deserialize(responseBody)

assertThat(responseEvent).isNotNull

val externalEventResponse = responseEvent?.payload as ExternalEventResponse
assertThat(externalEventResponse.payload).isNull()
assertThat(externalEventResponse.error).isNotNull()
}

@Test
fun `RPC endpoint does not accept request and returns back a 500 error`() {
val url = "${System.getProperty("cryptoWorkerUrl")}api/$PLATFORM_VERSION/crypto"

logger.info("crypto url: $url")
val serializedPayload = avroSerializer.serialize(generateByIdsFlowOpsRequest())

val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(serializedPayload))
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray())

assertThat(response.statusCode()).isEqualTo(404).withFailMessage("status code on response: ${response.statusCode()} url: $url")
}

private val testClock = AutoTickTestClock(Instant.MAX, Duration.ofSeconds(1))

/**
* Generate simple request to lookup for keys by their full key ids.
* Lookup will return no items in the response.
*/
private fun generateByIdsFlowOpsRequest(returnError: Boolean = false) : FlowOpsRequest {
val secureHash = SecureHashImpl("algorithm", "12345678".toByteArray()).toAvro()
val generateByIdsRequest = ByIdsFlowQuery(SecureHashes(listOf(secureHash)))

if (returnError) {
cryptoRequestContext.tenantId = UUID.randomUUID().toString()
}

return FlowOpsRequest.newBuilder()
.setContext(cryptoRequestContext)
.setRequest(generateByIdsRequest)
.setFlowExternalEventContext(externalEventContext)
.build()
}

private fun createRequestContext(): CryptoRequestContext = CryptoRequestContext(
"test-component",
Instant.now(),
UUID.randomUUID().toString(),
aliceHoldingId,
KeyValuePairList(
listOf(
KeyValuePair("key1", "value1"),
KeyValuePair("key2", "value2")
)
)
)

private fun assertResponseContext(expected: CryptoRequestContext, actual: CryptoResponseContext) {
val now = Instant.now()
assertEquals(expected.tenantId, actual.tenantId)
assertEquals(expected.requestId, actual.requestId)
assertEquals(expected.requestingComponent, actual.requestingComponent)
assertEquals(expected.requestTimestamp, actual.requestTimestamp)
assertThat(actual.responseTimestamp.epochSecond)
.isGreaterThanOrEqualTo(expected.requestTimestamp.epochSecond)
.isLessThanOrEqualTo(now.epochSecond)
assertSoftly { softly ->
softly.assertThat(actual.other.items.size == expected.other.items.size)
softly.assertThat(actual.other.items.containsAll(expected.other.items))
softly.assertThat(expected.other.items.containsAll(actual.other.items))
}
}

private fun assertStandardSuccessResponse(
response: FlowOpsResponse,
clock: AutoTickTestClock? = null
) = getResultOfType<FlowOpsResponse>(response)
.run { assertValidTimestamp(response.context.requestTimestamp, clock) }

private inline fun <reified T> getResultOfType(response: FlowOpsResponse): T {
Assertions.assertInstanceOf(T::class.java, response)
@Suppress("UNCHECKED_CAST")
return response as T
}

private fun assertValidTimestamp(timestamp: Instant, clock: AutoTickTestClock? = null) {
assertThat(timestamp).isAfter(Instant.MIN)
if (clock != null) {
assertThat(timestamp).isBeforeOrEqualTo(clock.peekTime())
}
}
}
2 changes: 1 addition & 1 deletion charts/corda-lib/templates/_worker.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ metadata:
spec:
type: ClusterIP
selector:
app: {{ $workerName }}
app.kubernetes.io/component: {{ include "corda.workerComponent" $worker }}
ports:
- protocol: TCP
port: {{ include "corda.workerServicePort" . }}
Expand Down
31 changes: 31 additions & 0 deletions components/crypto/crypto-rest/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id 'corda.common-publishing'
id 'corda.common-library'
}

description 'Crypto REST definition'

dependencies {
implementation project(':libs:lifecycle:lifecycle')
implementation project(':libs:platform-info')
implementation project(':libs:rest:rest')
implementation project(':libs:messaging:messaging')
implementation project(':libs:configuration:configuration-core')
implementation project(':components:configuration:configuration-read-service')
implementation project(':libs:crypto:crypto-core')
implementation project(':libs:crypto:crypto-config-impl')

compileOnly 'org.osgi:org.osgi.service.component.annotations'
compileOnly "org.osgi:osgi.annotation"

implementation platform("net.corda:corda-api:$cordaApiVersion")
implementation "net.corda:corda-avro-schema"
implementation 'net.corda:corda-base'
implementation "net.corda:corda-config-schema"
implementation "net.corda:corda-topic-schema"
implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'

testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation project(':libs:lifecycle:lifecycle-test-impl')
}
Loading