mirror of
https://github.com/corda/corda.git
synced 2025-01-15 09:20:22 +00:00
Adding jira integration to CRL (#579)
* Adding jira integration * Addressing review comments * Adding review comments * Fixes after rebase
This commit is contained in:
parent
c9caaf8be4
commit
101eee1ff5
@ -146,6 +146,7 @@ class NetworkParametersUpdateTest : IntegrationTest() {
|
|||||||
serverAddress,
|
serverAddress,
|
||||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||||
|
null,
|
||||||
if (startNetworkMap) {
|
if (startNetworkMap) {
|
||||||
NetworkMapStartParams(
|
NetworkMapStartParams(
|
||||||
LocalSigner(networkMapCa),
|
LocalSigner(networkMapCa),
|
||||||
|
@ -159,6 +159,13 @@ class NodeRegistrationTest : IntegrationTest() {
|
|||||||
serverAddress,
|
serverAddress,
|
||||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||||
|
CertificateRevocationConfig(
|
||||||
|
approveAll = true,
|
||||||
|
jira = null,
|
||||||
|
approveInterval = timeoutMillis,
|
||||||
|
crlCacheTimeout = timeoutMillis,
|
||||||
|
crlEndpoint = URL("http://test.com/crl"),
|
||||||
|
crlUpdateInterval = timeoutMillis),
|
||||||
if (startNetworkMap) {
|
if (startNetworkMap) {
|
||||||
NetworkMapStartParams(
|
NetworkMapStartParams(
|
||||||
LocalSigner(networkMapCa),
|
LocalSigner(networkMapCa),
|
||||||
|
@ -14,6 +14,7 @@ import com.nhaarman.mockito_kotlin.*
|
|||||||
import com.r3.corda.networkmanage.common.HOST
|
import com.r3.corda.networkmanage.common.HOST
|
||||||
import com.r3.corda.networkmanage.common.HsmBaseTest
|
import com.r3.corda.networkmanage.common.HsmBaseTest
|
||||||
import com.r3.corda.networkmanage.common.persistence.configureDatabase
|
import com.r3.corda.networkmanage.common.persistence.configureDatabase
|
||||||
|
import com.r3.corda.networkmanage.doorman.CertificateRevocationConfig
|
||||||
import com.r3.corda.networkmanage.doorman.DoormanConfig
|
import com.r3.corda.networkmanage.doorman.DoormanConfig
|
||||||
import com.r3.corda.networkmanage.doorman.NetworkManagementServer
|
import com.r3.corda.networkmanage.doorman.NetworkManagementServer
|
||||||
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
|
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
|
||||||
@ -26,6 +27,8 @@ import net.corda.core.internal.createDirectories
|
|||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.utilities.hours
|
||||||
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.NodeRegistrationOption
|
import net.corda.node.NodeRegistrationOption
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
@ -101,6 +104,14 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
|||||||
hostAndPort = NetworkHostAndPort(HOST, 0),
|
hostAndPort = NetworkHostAndPort(HOST, 0),
|
||||||
csrCertPathAndKey = null,
|
csrCertPathAndKey = null,
|
||||||
doormanConfig = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
doormanConfig = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
||||||
|
revocationConfig = CertificateRevocationConfig(
|
||||||
|
approveAll = true,
|
||||||
|
jira = null,
|
||||||
|
crlUpdateInterval = 2.hours.toMillis(),
|
||||||
|
crlCacheTimeout = 30.minutes.toMillis(),
|
||||||
|
crlEndpoint = URL("http://test.com/crl"),
|
||||||
|
approveInterval = 10.minutes.toMillis()
|
||||||
|
),
|
||||||
startNetworkMap = null)
|
startNetworkMap = null)
|
||||||
val doormanHostAndPort = server.hostAndPort
|
val doormanHostAndPort = server.hostAndPort
|
||||||
// Start Corda network registration.
|
// Start Corda network registration.
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.r3.corda.networkmanage.common.persistence
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage.Companion.DOORMAN_SIGNATURE
|
||||||
|
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This storage automatically approves all created requests.
|
||||||
|
*/
|
||||||
|
class ApproveAllCertificateRevocationRequestStorage(private val delegate: CertificateRevocationRequestStorage) : CertificateRevocationRequestStorage by delegate {
|
||||||
|
|
||||||
|
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
|
||||||
|
val requestId = delegate.saveRevocationRequest(request)
|
||||||
|
delegate.markRequestTicketCreated(requestId)
|
||||||
|
approveRevocationRequest(requestId, DOORMAN_SIGNATURE)
|
||||||
|
return requestId
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,10 @@ import java.time.Instant
|
|||||||
* Interface for managing certificate revocation list persistence
|
* Interface for managing certificate revocation list persistence
|
||||||
*/
|
*/
|
||||||
interface CertificateRevocationListStorage {
|
interface CertificateRevocationListStorage {
|
||||||
|
companion object {
|
||||||
|
val DOORMAN_SIGNATURE = "Doorman-Crl-Signer"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the latest certificate revocation list.
|
* Retrieves the latest certificate revocation list.
|
||||||
*
|
*
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.cert.CRLReason
|
import java.security.cert.CRLReason
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,6 +13,8 @@ import java.time.Instant
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string
|
data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string
|
||||||
|
val certificateSigningRequestId: String,
|
||||||
|
val certificate: X509Certificate,
|
||||||
val certificateSerialNumber: BigInteger,
|
val certificateSerialNumber: BigInteger,
|
||||||
val modifiedAt: Instant,
|
val modifiedAt: Instant,
|
||||||
val legalName: CordaX500Name,
|
val legalName: CordaX500Name,
|
||||||
@ -23,6 +26,9 @@ data class CertificateRevocationRequestData(val requestId: String, // This is a
|
|||||||
* Interface for managing certificate revocation requests persistence
|
* Interface for managing certificate revocation requests persistence
|
||||||
*/
|
*/
|
||||||
interface CertificateRevocationRequestStorage {
|
interface CertificateRevocationRequestStorage {
|
||||||
|
companion object {
|
||||||
|
val DOORMAN_SIGNATURE = "Doorman-Crr-Signer"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new revocation request for the given [certificateSerialNumber].
|
* Creates a new revocation request for the given [certificateSerialNumber].
|
||||||
@ -70,5 +76,10 @@ interface CertificateRevocationRequestStorage {
|
|||||||
* @param rejectedBy who is rejecting it
|
* @param rejectedBy who is rejecting it
|
||||||
* @param reason description of the reason of this rejection.
|
* @param reason description of the reason of this rejection.
|
||||||
*/
|
*/
|
||||||
fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String)
|
fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the fact that a ticket has been created for the given [requestId].
|
||||||
|
*/
|
||||||
|
fun markRequestTicketCreated(requestId: String)
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ data class CertificateSigningRequest(val requestId: String,
|
|||||||
*/
|
*/
|
||||||
interface CertificateSigningRequestStorage {
|
interface CertificateSigningRequestStorage {
|
||||||
companion object {
|
companion object {
|
||||||
val DOORMAN_SIGNATURE = "Doorman"
|
val DOORMAN_SIGNATURE = "Doorman-Csr-Signer"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
|||||||
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
||||||
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
|
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage {
|
class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage {
|
||||||
@ -129,7 +130,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String) {
|
override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?) {
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val revocation = getRevocationRequestEntity(requestId)
|
val revocation = getRevocationRequestEntity(requestId)
|
||||||
if (revocation == null) {
|
if (revocation == null) {
|
||||||
@ -145,10 +146,28 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? {
|
override fun markRequestTicketCreated(requestId: String) {
|
||||||
|
// Even though, we have an assumption that there is always a single instance of the doorman service running,
|
||||||
|
// the SERIALIZABLE isolation level is used here just to ensure data consistency between the updates.
|
||||||
|
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
|
||||||
|
val request = getRevocationRequestEntity(requestId, RequestStatus.NEW)
|
||||||
|
request ?: throw IllegalArgumentException("Error when creating request ticket with id: $requestId. Request does not exist or its status is not NEW.")
|
||||||
|
val update = request.copy(
|
||||||
|
modifiedAt = Instant.now(),
|
||||||
|
status = RequestStatus.TICKET_CREATED)
|
||||||
|
session.merge(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRevocationRequestEntity(requestId: String, status: RequestStatus? = null): CertificateRevocationRequestEntity? {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
uniqueEntityWhere { builder, path ->
|
uniqueEntityWhere { builder, path ->
|
||||||
builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
|
val idEqual = builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
|
||||||
|
if (status == null) {
|
||||||
|
idEqual
|
||||||
|
} else {
|
||||||
|
builder.and(idEqual, builder.equal(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), status))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,6 +175,8 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
|
|||||||
private fun CertificateRevocationRequestEntity.toCertificateRevocationRequestData(): CertificateRevocationRequestData {
|
private fun CertificateRevocationRequestEntity.toCertificateRevocationRequestData(): CertificateRevocationRequestData {
|
||||||
return CertificateRevocationRequestData(
|
return CertificateRevocationRequestData(
|
||||||
requestId,
|
requestId,
|
||||||
|
certificateData.certificateSigningRequest.requestId,
|
||||||
|
certificateData.certPath.certificates.first() as X509Certificate,
|
||||||
certificateSerialNumber,
|
certificateSerialNumber,
|
||||||
modifiedAt,
|
modifiedAt,
|
||||||
legalName,
|
legalName,
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* R3 Proprietary and Confidential
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||||
|
*
|
||||||
|
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||||
|
*
|
||||||
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.r3.corda.networkmanage.doorman
|
||||||
|
|
||||||
|
import com.atlassian.jira.rest.client.api.JiraRestClient
|
||||||
|
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder
|
||||||
|
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
|
||||||
|
class CrrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) {
|
||||||
|
companion object {
|
||||||
|
val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCertificateRevocationRequestTicket(revocationRequest: CertificateRevocationRequestData) {
|
||||||
|
// Check there isn't already a ticket for this request.
|
||||||
|
if (getIssueById(revocationRequest.requestId) != null) {
|
||||||
|
logger.warn("There is already a ticket corresponding to request Id ${revocationRequest.requestId}, not creating a new one.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val ticketDescription = "Legal name: ${revocationRequest.legalName}\n" +
|
||||||
|
"Certificate serial number: ${revocationRequest.certificateSerialNumber}\n" +
|
||||||
|
"Revocation reason: ${revocationRequest.reason.name}\n" +
|
||||||
|
"Reporter: ${revocationRequest.reporter}\n" +
|
||||||
|
"CSR request ID: ${revocationRequest.certificateSigningRequestId}"
|
||||||
|
|
||||||
|
val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id)
|
||||||
|
.setProjectKey(projectCode)
|
||||||
|
.setDescription(ticketDescription)
|
||||||
|
.setFieldValue(requestIdField.id, revocationRequest.requestId)
|
||||||
|
// This will block until the issue is created.
|
||||||
|
val issueId = restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim().key
|
||||||
|
val createdIssue = checkNotNull(getIssueById(issueId)) { "Missing the JIRA ticket for the request ID: $issueId" }
|
||||||
|
restClient.issueClient.addAttachment(createdIssue.attachmentsUri, revocationRequest.certificate.encoded.inputStream(), "${revocationRequest.certificateSerialNumber}.cer")
|
||||||
|
.fail { CsrJiraClient.logger.error("Error processing request '${createdIssue.key}' : Exception when uploading attachment to JIRA.", it) }.claim()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDoneCertificateRevocationRequests(doneRequests: List<String>) {
|
||||||
|
doneRequests.forEach { id ->
|
||||||
|
val issue = getIssueById(id)
|
||||||
|
issue ?: throw IllegalStateException("Missing the JIRA ticket for the request ID: $id")
|
||||||
|
if (doneTransitionId == -1) {
|
||||||
|
doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id
|
||||||
|
}
|
||||||
|
restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* R3 Proprietary and Confidential
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||||
|
*
|
||||||
|
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||||
|
*
|
||||||
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.r3.corda.networkmanage.doorman
|
||||||
|
|
||||||
|
import com.atlassian.jira.rest.client.api.JiraRestClient
|
||||||
|
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder
|
||||||
|
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput
|
||||||
|
import com.r3.corda.networkmanage.common.utils.getCertRole
|
||||||
|
import com.r3.corda.networkmanage.common.utils.getEmail
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||||
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||||
|
import org.bouncycastle.util.io.pem.PemObject
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.security.cert.CertPath
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
|
class CsrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) {
|
||||||
|
companion object {
|
||||||
|
val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pass in a parsed object instead of raw PKCS10 request.
|
||||||
|
fun createCertificateSigningRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) {
|
||||||
|
// Check there isn't already a ticket for this request.
|
||||||
|
if (getIssueById(requestId) != null) {
|
||||||
|
logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure request has been accepted.
|
||||||
|
val request = StringWriter()
|
||||||
|
JcaPEMWriter(request).use {
|
||||||
|
it.writeObject(PemObject("CERTIFICATE REQUEST", signingRequest.encoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO The subject of the signing request has already been validated and parsed into a CordaX500Name. We shouldn't
|
||||||
|
// have to do it again here.
|
||||||
|
val subject = CordaX500Name.build(X500Principal(signingRequest.subject.encoded))
|
||||||
|
val email = signingRequest.getEmail()
|
||||||
|
|
||||||
|
val certRole = signingRequest.getCertRole()
|
||||||
|
|
||||||
|
val ticketSummary = if (subject.organisationUnit != null) {
|
||||||
|
"${subject.organisationUnit}, ${subject.organisation}"
|
||||||
|
} else {
|
||||||
|
subject.organisation
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = mapOf("Requested Role Type" to certRole.name,
|
||||||
|
"Organisation" to subject.organisation,
|
||||||
|
"Organisation Unit" to subject.organisationUnit,
|
||||||
|
"Nearest City" to subject.locality,
|
||||||
|
"Country" to subject.country,
|
||||||
|
"Email" to email)
|
||||||
|
|
||||||
|
val ticketDescription = data.filter { it.value != null }.map { "${it.key}: ${it.value}" }.joinToString("\n") + "\n\n{code}$request{code}"
|
||||||
|
|
||||||
|
val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id)
|
||||||
|
.setProjectKey(projectCode)
|
||||||
|
.setDescription(ticketDescription)
|
||||||
|
.setSummary(ticketSummary)
|
||||||
|
.setFieldValue(requestIdField.id, requestId)
|
||||||
|
// This will block until the issue is created.
|
||||||
|
restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDoneCertificateSigningRequests(signedRequests: Map<String, CertPath>) {
|
||||||
|
// Retrieving certificates for signed CSRs to attach to the jira tasks.
|
||||||
|
signedRequests.forEach { (id, certPath) ->
|
||||||
|
val certificate = certPath.certificates.first()
|
||||||
|
val issue = getIssueById(id)
|
||||||
|
if (issue != null) {
|
||||||
|
if (doneTransitionId == -1) {
|
||||||
|
doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id
|
||||||
|
}
|
||||||
|
restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim()
|
||||||
|
restClient.issueClient.addAttachment(issue.attachmentsUri, certificate.encoded.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer")
|
||||||
|
.fail { logger.error("Error processing request '${issue.key}' : Exception when uploading attachment to JIRA.", it) }.claim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ import com.google.common.primitives.Booleans
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ data class NetworkManagementServerConfig( // TODO: Move local signing to signing
|
|||||||
|
|
||||||
val doorman: DoormanConfig?,
|
val doorman: DoormanConfig?,
|
||||||
val networkMap: NetworkMapConfig?,
|
val networkMap: NetworkMapConfig?,
|
||||||
|
val revocation: CertificateRevocationConfig?,
|
||||||
|
|
||||||
// TODO Should be part of a localSigning sub-config
|
// TODO Should be part of a localSigning sub-config
|
||||||
val keystorePath: Path? = null,
|
val keystorePath: Path? = null,
|
||||||
@ -54,6 +56,19 @@ data class DoormanConfig(val approveAll: Boolean = false,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CertificateRevocationConfig(val approveAll: Boolean = false,
|
||||||
|
val jira: JiraConfig? = null,
|
||||||
|
val crlUpdateInterval: Long,
|
||||||
|
val crlEndpoint: URL,
|
||||||
|
val crlCacheTimeout: Long,
|
||||||
|
val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) {
|
||||||
|
init {
|
||||||
|
require(Booleans.countTrue(approveAll, jira != null) == 1) {
|
||||||
|
"Either 'approveAll' or 'jira' config settings need to be specified but not both"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class NetworkMapConfig(val cacheTimeout: Long,
|
data class NetworkMapConfig(val cacheTimeout: Long,
|
||||||
// TODO: Move signing to signing server.
|
// TODO: Move signing to signing server.
|
||||||
val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis())
|
val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis())
|
||||||
|
@ -16,80 +16,23 @@ import com.atlassian.jira.rest.client.api.domain.Comment
|
|||||||
import com.atlassian.jira.rest.client.api.domain.Field
|
import com.atlassian.jira.rest.client.api.domain.Field
|
||||||
import com.atlassian.jira.rest.client.api.domain.Issue
|
import com.atlassian.jira.rest.client.api.domain.Issue
|
||||||
import com.atlassian.jira.rest.client.api.domain.IssueType
|
import com.atlassian.jira.rest.client.api.domain.IssueType
|
||||||
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder
|
|
||||||
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput
|
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput
|
||||||
import com.r3.corda.networkmanage.common.utils.getCertRole
|
|
||||||
import com.r3.corda.networkmanage.common.utils.getEmail
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.internal.CertRole
|
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
|
||||||
import org.bouncycastle.util.io.pem.PemObject
|
|
||||||
import java.io.StringWriter
|
|
||||||
import java.security.cert.CertPath
|
|
||||||
import javax.security.auth.x500.X500Principal
|
|
||||||
|
|
||||||
class JiraClient(private val restClient: JiraRestClient, private val projectCode: String) {
|
abstract class JiraClient(protected val restClient: JiraRestClient, protected val projectCode: String) {
|
||||||
companion object {
|
companion object {
|
||||||
val logger = contextLogger()
|
val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The JIRA project must have a Request ID and reject reason field, and the Task issue type.
|
// The JIRA project must have a Request ID and reject reason field, and the Task issue type.
|
||||||
private val requestIdField: Field = restClient.metadataClient.fields.claim().find { it.name == "Request ID" } ?: throw IllegalArgumentException("Request ID field not found in JIRA '$projectCode'")
|
protected val requestIdField: Field = restClient.metadataClient.fields.claim().find { it.name == "Request ID" } ?: throw IllegalArgumentException("Request ID field not found in JIRA '$projectCode'")
|
||||||
private val rejectReasonField: Field = restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" } ?: throw IllegalArgumentException("Reject Reason field not found in JIRA '$projectCode'")
|
protected val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'")
|
||||||
private val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'")
|
protected val rejectReasonField: Field = restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" } ?: throw IllegalArgumentException("Reject Reason field not found in JIRA '$projectCode'")
|
||||||
|
|
||||||
private var doneTransitionId: Int = -1
|
protected var doneTransitionId: Int = -1
|
||||||
private var canceledTransitionId: Int = -1
|
private var canceledTransitionId: Int = -1
|
||||||
private var startProgressTransitionId: Int = -1
|
private var startProgressTransitionId: Int = -1
|
||||||
|
|
||||||
// TODO: Pass in a parsed object instead of raw PKCS10 request.
|
|
||||||
fun createRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) {
|
|
||||||
// Check there isn't already a ticket for this request.
|
|
||||||
if (getIssueById(requestId) != null) {
|
|
||||||
logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure request has been accepted.
|
|
||||||
val request = StringWriter()
|
|
||||||
JcaPEMWriter(request).use {
|
|
||||||
it.writeObject(PemObject("CERTIFICATE REQUEST", signingRequest.encoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO The subject of the signing request has already been validated and parsed into a CordaX500Name. We shouldn't
|
|
||||||
// have to do it again here.
|
|
||||||
val subject = CordaX500Name.build(X500Principal(signingRequest.subject.encoded))
|
|
||||||
val email = signingRequest.getEmail()
|
|
||||||
|
|
||||||
val certRole = signingRequest.getCertRole()
|
|
||||||
|
|
||||||
val ticketSummary = if (subject.organisationUnit != null) {
|
|
||||||
"${subject.organisationUnit}, ${subject.organisation}"
|
|
||||||
} else {
|
|
||||||
subject.organisation
|
|
||||||
}
|
|
||||||
|
|
||||||
val data = mapOf("Requested Role Type" to certRole.name,
|
|
||||||
"Organisation" to subject.organisation,
|
|
||||||
"Organisation Unit" to subject.organisationUnit,
|
|
||||||
"Nearest City" to subject.locality,
|
|
||||||
"Country" to subject.country,
|
|
||||||
"Email" to email)
|
|
||||||
|
|
||||||
val ticketDescription = data.filter { it.value != null }.map { "${it.key}: ${it.value}" }.joinToString("\n") + "\n\n{code}$request{code}"
|
|
||||||
|
|
||||||
val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id)
|
|
||||||
.setProjectKey(projectCode)
|
|
||||||
.setDescription(ticketDescription)
|
|
||||||
.setSummary(ticketSummary)
|
|
||||||
.setFieldValue(requestIdField.id, requestId)
|
|
||||||
// This will block until the issue is created.
|
|
||||||
restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getApprovedRequests(): List<ApprovedRequest> {
|
fun getApprovedRequests(): List<ApprovedRequest> {
|
||||||
val issues = restClient.searchClient.searchJql("project = $projectCode AND status = Approved").claim().issues
|
val issues = restClient.searchClient.searchJql("project = $projectCode AND status = Approved").claim().issues
|
||||||
return issues.mapNotNull { issue ->
|
return issues.mapNotNull { issue ->
|
||||||
@ -113,22 +56,6 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSignedRequests(signedRequests: Map<String, CertPath>) {
|
|
||||||
// Retrieving certificates for signed CSRs to attach to the jira tasks.
|
|
||||||
signedRequests.forEach { (id, certPath) ->
|
|
||||||
val certificate = certPath.certificates.first()
|
|
||||||
val issue = getIssueById(id)
|
|
||||||
if (issue != null) {
|
|
||||||
if (doneTransitionId == -1) {
|
|
||||||
doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id
|
|
||||||
}
|
|
||||||
restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim()
|
|
||||||
restClient.issueClient.addAttachment(issue.attachmentsUri, certificate.encoded.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer")
|
|
||||||
.fail { logger.error("Error processing request '${issue.key}' : Exception when uploading attachment to JIRA.", it) }.claim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateRejectedRequests(rejectedRequests: List<String>) {
|
fun updateRejectedRequests(rejectedRequests: List<String>) {
|
||||||
rejectedRequests.mapNotNull { getIssueById(it) }
|
rejectedRequests.mapNotNull { getIssueById(it) }
|
||||||
.forEach { issue ->
|
.forEach { issue ->
|
||||||
@ -146,7 +73,7 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIssueById(requestId: String): Issue? {
|
protected fun getIssueById(requestId: String): Issue? {
|
||||||
// Jira only support ~ (contains) search for custom textfield.
|
// Jira only support ~ (contains) search for custom textfield.
|
||||||
return restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull()
|
return restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull()
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkMa
|
|||||||
config.address,
|
config.address,
|
||||||
csrAndNetworkMap?.first,
|
csrAndNetworkMap?.first,
|
||||||
config.doorman,
|
config.doorman,
|
||||||
|
config.revocation,
|
||||||
networkMapStartParams)
|
networkMapStartParams)
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") {
|
Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") {
|
||||||
|
@ -14,16 +14,13 @@ import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientF
|
|||||||
import com.r3.corda.networkmanage.common.persistence.*
|
import com.r3.corda.networkmanage.common.persistence.*
|
||||||
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
|
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
|
||||||
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
|
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
|
||||||
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
|
import com.r3.corda.networkmanage.doorman.signer.*
|
||||||
import com.r3.corda.networkmanage.doorman.signer.JiraCsrHandler
|
import com.r3.corda.networkmanage.doorman.webservice.*
|
||||||
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
|
|
||||||
import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService
|
|
||||||
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService
|
|
||||||
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
@ -48,7 +45,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
logger.info("Closing server...")
|
logger.info("Closing server...")
|
||||||
for (closeAction in closeActions) {
|
for (closeAction in closeActions.reversed()) {
|
||||||
try {
|
try {
|
||||||
closeAction()
|
closeAction()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -96,7 +93,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
|||||||
val jiraConfig = config.jira
|
val jiraConfig = config.jira
|
||||||
val requestProcessor = if (jiraConfig != null) {
|
val requestProcessor = if (jiraConfig != null) {
|
||||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
||||||
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode)
|
val jiraClient = CsrJiraClient(jiraWebAPI, jiraConfig.projectCode)
|
||||||
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey))
|
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey))
|
||||||
} else {
|
} else {
|
||||||
DefaultCsrHandler(requestService, csrCertPathAndKey)
|
DefaultCsrHandler(requestService, csrCertPathAndKey)
|
||||||
@ -119,9 +116,51 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
|||||||
return RegistrationWebService(requestProcessor, Duration.ofMillis(config.approveInterval))
|
return RegistrationWebService(requestProcessor, Duration.ofMillis(config.approveInterval))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getRevocationServices(config: CertificateRevocationConfig,
|
||||||
|
database: CordaPersistence,
|
||||||
|
csrCertPathAndKeyPair: CertPathAndKey?): Pair<CertificateRevocationRequestWebService, CertificateRevocationListWebService> {
|
||||||
|
logger.info("Starting Revocation server.")
|
||||||
|
val crrStorage = if (config.approveAll) {
|
||||||
|
logger.warn("Revocation server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
|
||||||
|
ApproveAllCertificateRevocationRequestStorage(PersistentCertificateRevocationRequestStorage(database))
|
||||||
|
} else {
|
||||||
|
PersistentCertificateRevocationRequestStorage(database)
|
||||||
|
}
|
||||||
|
val crlStorage = PersistentCertificateRevocationListStorage(database)
|
||||||
|
|
||||||
|
val crlHandler = csrCertPathAndKeyPair?.let {
|
||||||
|
LocalCrlHandler(crrStorage, crlStorage, CertificateAndKeyPair(it.certPath.first(), it.toKeyPair()), Duration.ofMillis(config.crlUpdateInterval), config.crlEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
val jiraConfig = config.jira
|
||||||
|
val crrHandler = if (jiraConfig != null) {
|
||||||
|
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
||||||
|
val jiraClient = CrrJiraClient(jiraWebAPI, jiraConfig.projectCode)
|
||||||
|
JiraCrrHandler(jiraClient, crrStorage, crlHandler)
|
||||||
|
} else {
|
||||||
|
DefaultCrrHandler(crrStorage, crlHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
val scheduledExecutor = Executors.newScheduledThreadPool(1)
|
||||||
|
val approvalThread = Runnable {
|
||||||
|
try {
|
||||||
|
// Process Jira approved tickets.
|
||||||
|
crrHandler.processRequests()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log the error and carry on.
|
||||||
|
logger.error("Error encountered when approving request.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scheduledExecutor.scheduleAtFixedRate(approvalThread, config.approveInterval, config.approveInterval, TimeUnit.MILLISECONDS)
|
||||||
|
closeActions += scheduledExecutor::shutdown
|
||||||
|
// TODO start socket server
|
||||||
|
return Pair(CertificateRevocationRequestWebService(crrHandler), CertificateRevocationListWebService(crlStorage, Duration.ofMillis(config.crlCacheTimeout)))
|
||||||
|
}
|
||||||
|
|
||||||
fun start(hostAndPort: NetworkHostAndPort,
|
fun start(hostAndPort: NetworkHostAndPort,
|
||||||
csrCertPathAndKey: CertPathAndKey?,
|
csrCertPathAndKey: CertPathAndKey?,
|
||||||
doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
||||||
|
revocationConfig: CertificateRevocationConfig?,
|
||||||
startNetworkMap: NetworkMapStartParams?
|
startNetworkMap: NetworkMapStartParams?
|
||||||
) {
|
) {
|
||||||
val services = mutableListOf<Any>()
|
val services = mutableListOf<Any>()
|
||||||
@ -129,6 +168,11 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
|||||||
|
|
||||||
startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) }
|
startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) }
|
||||||
doormanConfig?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
|
doormanConfig?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
|
||||||
|
revocationConfig?.let {
|
||||||
|
val revocationServices = getRevocationServices(it, database, csrCertPathAndKey)
|
||||||
|
services += revocationServices.first
|
||||||
|
services += revocationServices.second
|
||||||
|
}
|
||||||
|
|
||||||
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
|
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
|
||||||
|
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* R3 Proprietary and Confidential
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||||
|
*
|
||||||
|
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||||
|
*
|
||||||
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.r3.corda.networkmanage.doorman.signer
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.RequestStatus
|
||||||
|
import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
|
import java.net.URL
|
||||||
|
import java.time.Duration
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage.Companion.DOORMAN_SIGNATURE
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.trace
|
||||||
|
|
||||||
|
class LocalCrlHandler(private val crrStorage: CertificateRevocationRequestStorage,
|
||||||
|
crlStorage: CertificateRevocationListStorage,
|
||||||
|
issuerCertAndKey: CertificateAndKeyPair,
|
||||||
|
crlUpdateInterval: Duration,
|
||||||
|
crlEndpoint: URL) {
|
||||||
|
private companion object {
|
||||||
|
private val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val crlSigner = CertificateRevocationListSigner(
|
||||||
|
crlStorage,
|
||||||
|
issuerCertAndKey.certificate,
|
||||||
|
crlUpdateInterval,
|
||||||
|
crlEndpoint,
|
||||||
|
LocalSigner(issuerCertAndKey))
|
||||||
|
|
||||||
|
fun signCrl() {
|
||||||
|
logger.info("Executing CRL signing...")
|
||||||
|
val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED)
|
||||||
|
logger.debug("Approved certificate revocation requests retrieved.")
|
||||||
|
logger.trace { approvedRequests.toString() }
|
||||||
|
if (approvedRequests.isEmpty()) {
|
||||||
|
// Nothing to add to the current CRL
|
||||||
|
logger.debug("There are no APPROVED certificate revocation requests. Aborting CRL signing.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE)
|
||||||
|
logger.debug("Existing certificate revocation requests retrieved.")
|
||||||
|
logger.trace { currentRequests.toString() }
|
||||||
|
crlSigner.createSignedCRL(approvedRequests, currentRequests, DOORMAN_SIGNATURE)
|
||||||
|
logger.info("New CRL signed.")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* R3 Proprietary and Confidential
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||||
|
*
|
||||||
|
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||||
|
*
|
||||||
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.r3.corda.networkmanage.doorman.signer
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
|
||||||
|
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
||||||
|
|
||||||
|
interface CrrHandler {
|
||||||
|
fun processRequests()
|
||||||
|
fun saveRevocationRequest(request: CertificateRevocationRequest): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultCrrHandler(private val crrStorage: CertificateRevocationRequestStorage,
|
||||||
|
private val localCrlHandler: LocalCrlHandler?) : CrrHandler {
|
||||||
|
override fun saveRevocationRequest(request: CertificateRevocationRequest): String = crrStorage.saveRevocationRequest(request)
|
||||||
|
override fun processRequests() {
|
||||||
|
localCrlHandler?.signCrl()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* R3 Proprietary and Confidential
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||||
|
*
|
||||||
|
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||||
|
*
|
||||||
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.r3.corda.networkmanage.doorman.signer
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
|
||||||
|
import com.r3.corda.networkmanage.common.persistence.RequestStatus
|
||||||
|
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
||||||
|
import com.r3.corda.networkmanage.doorman.CrrJiraClient
|
||||||
|
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
||||||
|
|
||||||
|
class JiraCrrHandler(private val jiraClient: CrrJiraClient,
|
||||||
|
private val crrStorage: CertificateRevocationRequestStorage,
|
||||||
|
private val localCrlHandler: LocalCrlHandler?) : CrrHandler {
|
||||||
|
private companion object {
|
||||||
|
val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
|
||||||
|
try {
|
||||||
|
val requestId = crrStorage.saveRevocationRequest(request)
|
||||||
|
val requestData = crrStorage.getRevocationRequest(requestId)
|
||||||
|
requestData ?: throw IllegalStateException("Request $requestId does not exist.")
|
||||||
|
jiraClient.createCertificateRevocationRequestTicket(requestData)
|
||||||
|
crrStorage.markRequestTicketCreated(requestId)
|
||||||
|
return requestId
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("There was an error while creating JIRA tickets", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processRequests() {
|
||||||
|
createTickets()
|
||||||
|
val (approvedRequests, rejectedRequests) = updateRequestStatuses()
|
||||||
|
updateJiraTickets(approvedRequests, rejectedRequests)
|
||||||
|
localCrlHandler?.signCrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateRequestStatuses(): Pair<List<ApprovedRequest>, List<RejectedRequest>> {
|
||||||
|
// Update local request statuses.
|
||||||
|
val approvedRequest = jiraClient.getApprovedRequests()
|
||||||
|
approvedRequest.forEach { (id, approvedBy) -> crrStorage.approveRevocationRequest(id, approvedBy) }
|
||||||
|
val rejectedRequest = jiraClient.getRejectedRequests()
|
||||||
|
rejectedRequest.forEach { (id, rejectedBy, reason) -> crrStorage.rejectRevocationRequest(id, rejectedBy, reason) }
|
||||||
|
return Pair(approvedRequest, rejectedRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateJiraTickets(approvedRequest: List<ApprovedRequest>, rejectedRequest: List<RejectedRequest>) {
|
||||||
|
// Reconfirm request status and update jira status
|
||||||
|
val doneRequests = approvedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) }
|
||||||
|
.filter { it.status == RequestStatus.DONE }
|
||||||
|
.map { it.requestId }
|
||||||
|
|
||||||
|
jiraClient.updateDoneCertificateRevocationRequests(doneRequests)
|
||||||
|
|
||||||
|
val rejectedRequestIDs = rejectedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) }
|
||||||
|
.filter { it.status == RequestStatus.REJECTED }
|
||||||
|
.map { it.requestId }
|
||||||
|
jiraClient.updateRejectedRequests(rejectedRequestIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Jira tickets for all request in [RequestStatus.NEW] state.
|
||||||
|
*
|
||||||
|
* Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately,
|
||||||
|
* they might be left in the [RequestStatus.NEW] state if Jira is down.
|
||||||
|
*/
|
||||||
|
private fun createTickets() {
|
||||||
|
crrStorage.getRevocationRequests(RequestStatus.NEW).forEach {
|
||||||
|
try {
|
||||||
|
createTicket(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("There were errors while creating Jira tickets for request '${it.requestId}'", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTicket(revocationRequest: CertificateRevocationRequestData) {
|
||||||
|
jiraClient.createCertificateRevocationRequestTicket(revocationRequest)
|
||||||
|
crrStorage.markRequestTicketCreated(revocationRequest.requestId)
|
||||||
|
}
|
||||||
|
}
|
@ -15,12 +15,12 @@ import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
|
|||||||
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
|
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
|
||||||
import com.r3.corda.networkmanage.common.persistence.RequestStatus
|
import com.r3.corda.networkmanage.common.persistence.RequestStatus
|
||||||
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
||||||
import com.r3.corda.networkmanage.doorman.JiraClient
|
import com.r3.corda.networkmanage.doorman.CsrJiraClient
|
||||||
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||||
|
|
||||||
class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: CertificateSigningRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate {
|
class JiraCsrHandler(private val jiraClient: CsrJiraClient, private val storage: CertificateSigningRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate {
|
||||||
private companion object {
|
private companion object {
|
||||||
val log = contextLogger()
|
val log = contextLogger()
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce
|
|||||||
// Make sure request has been accepted.
|
// Make sure request has been accepted.
|
||||||
try {
|
try {
|
||||||
if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) {
|
if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) {
|
||||||
jiraClient.createRequestTicket(requestId, rawRequest)
|
jiraClient.createCertificateSigningRequestTicket(requestId, rawRequest)
|
||||||
storage.markRequestTicketCreated(requestId)
|
storage.markRequestTicketCreated(requestId)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -62,7 +62,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce
|
|||||||
.filter { it.status == RequestStatus.DONE && it.certData != null }
|
.filter { it.status == RequestStatus.DONE && it.certData != null }
|
||||||
.associateBy { it.requestId }
|
.associateBy { it.requestId }
|
||||||
.mapValues { it.value.certData!!.certPath }
|
.mapValues { it.value.certData!!.certPath }
|
||||||
jiraClient.updateSignedRequests(signedRequests)
|
jiraClient.updateDoneCertificateSigningRequests(signedRequests)
|
||||||
|
|
||||||
val rejectedRequestIDs = rejectedRequest.mapNotNull { storage.getRequest(it.requestId) }
|
val rejectedRequestIDs = rejectedRequest.mapNotNull { storage.getRequest(it.requestId) }
|
||||||
.filter { it.status == RequestStatus.REJECTED }
|
.filter { it.status == RequestStatus.REJECTED }
|
||||||
@ -87,7 +87,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createTicket(signingRequest: CertificateSigningRequest) {
|
private fun createTicket(signingRequest: CertificateSigningRequest) {
|
||||||
jiraClient.createRequestTicket(signingRequest.requestId, signingRequest.request)
|
jiraClient.createCertificateSigningRequestTicket(signingRequest.requestId, signingRequest.request)
|
||||||
storage.markRequestTicketCreated(signingRequest.requestId)
|
storage.markRequestTicketCreated(signingRequest.requestId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListSt
|
|||||||
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
|
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
|
||||||
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH
|
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import java.time.Duration
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.ws.rs.GET
|
import javax.ws.rs.GET
|
||||||
import javax.ws.rs.Path
|
import javax.ws.rs.Path
|
||||||
@ -16,7 +17,7 @@ import javax.ws.rs.core.Response.status
|
|||||||
|
|
||||||
@Path(CRL_PATH)
|
@Path(CRL_PATH)
|
||||||
class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage,
|
class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage,
|
||||||
cacheTimeout: Long) {
|
cacheTimeout: Duration) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
const val CRL_PATH = "certificate-revocation-list"
|
const val CRL_PATH = "certificate-revocation-list"
|
||||||
@ -26,7 +27,7 @@ class CertificateRevocationListWebService(private val revocationListStorage: Cer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val crlCache: LoadingCache<CrlIssuer, ByteArray> = Caffeine.newBuilder()
|
private val crlCache: LoadingCache<CrlIssuer, ByteArray> = Caffeine.newBuilder()
|
||||||
.expireAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS)
|
.expireAfterWrite(cacheTimeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
.build({ key ->
|
.build({ key ->
|
||||||
revocationListStorage.getCertificateRevocationList(key)?.encoded
|
revocationListStorage.getCertificateRevocationList(key)?.encoded
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.r3.corda.networkmanage.doorman.webservice
|
package com.r3.corda.networkmanage.doorman.webservice
|
||||||
|
|
||||||
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
|
import com.r3.corda.networkmanage.doorman.signer.CrrHandler
|
||||||
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH
|
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
|
||||||
@ -14,7 +14,7 @@ import javax.ws.rs.core.Response
|
|||||||
import javax.ws.rs.core.Response.ok
|
import javax.ws.rs.core.Response.ok
|
||||||
|
|
||||||
@Path(CRR_PATH)
|
@Path(CRR_PATH)
|
||||||
class CertificateRevocationRequestWebService(private val revocationRequestStorage: CertificateRevocationRequestStorage) {
|
class CertificateRevocationRequestWebService(private val crrHandler: CrrHandler) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CRR_PATH = "certificate-revocation-request"
|
const val CRR_PATH = "certificate-revocation-request"
|
||||||
@ -25,7 +25,7 @@ class CertificateRevocationRequestWebService(private val revocationRequestStorag
|
|||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
fun submitRequest(input: InputStream): Response {
|
fun submitRequest(input: InputStream): Response {
|
||||||
val request = input.readBytes().deserialize<CertificateRevocationRequest>()
|
val request = input.readBytes().deserialize<CertificateRevocationRequest>()
|
||||||
val requestId = revocationRequestStorage.saveRevocationRequest(request)
|
val requestId = crrHandler.saveRevocationRequest(request)
|
||||||
return ok(requestId).build()
|
return ok(requestId).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,6 +62,8 @@ class CertificateRevocationListSignerTest : TestBase() {
|
|||||||
private fun givenCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData {
|
private fun givenCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData {
|
||||||
return CertificateRevocationRequestData(
|
return CertificateRevocationRequestData(
|
||||||
SecureHash.randomSHA256().toString(),
|
SecureHash.randomSHA256().toString(),
|
||||||
|
"CSR-ID-1",
|
||||||
|
mock(),
|
||||||
BigInteger.valueOf(random63BitValue()),
|
BigInteger.valueOf(random63BitValue()),
|
||||||
Instant.now(),
|
Instant.now(),
|
||||||
CordaX500Name.parse("CN=Bank A, O=$status, L=London, C=GB"),
|
CordaX500Name.parse("CN=Bank A, O=$status, L=London, C=GB"),
|
||||||
|
@ -23,18 +23,18 @@ import javax.security.auth.x500.X500Principal
|
|||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
// This is manual test for testing Jira API.
|
// This is manual test for testing Jira API.
|
||||||
class JiraClientTest {
|
class CsrJiraClientTest {
|
||||||
private lateinit var jiraClient: JiraClient
|
private lateinit var jiraClient: CsrJiraClient
|
||||||
@Before
|
@Before
|
||||||
fun init() {
|
fun init() {
|
||||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI("http://jira.url.com"), "username", "password")
|
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI("http://jira.url.com"), "username", "password")
|
||||||
jiraClient = JiraClient(jiraWebAPI, "DOOR")
|
jiraClient = CsrJiraClient(jiraWebAPI, "DOOR")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun createRequestTicket() {
|
fun createRequestTicket() {
|
||||||
val request = X509Utilities.createCertificateSigningRequest(CordaX500Name("JiraAPITest", "R3 Ltd 3", "London", "GB").x500Principal, "test@test.com", Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
val request = X509Utilities.createCertificateSigningRequest(CordaX500Name("JiraAPITest", "R3 Ltd 3", "London", "GB").x500Principal, "test@test.com", Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||||
jiraClient.createRequestTicket(SecureHash.randomSHA256().toString(), request)
|
jiraClient.createCertificateSigningRequestTicket(SecureHash.randomSHA256().toString(), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -54,7 +54,7 @@ class JiraClientTest {
|
|||||||
val selfSignedCaCertPath = X509Utilities.buildCertPath(X509Utilities.createSelfSignedCACertificate(
|
val selfSignedCaCertPath = X509Utilities.buildCertPath(X509Utilities.createSelfSignedCACertificate(
|
||||||
X500Principal("O=test,L=london,C=GB"),
|
X500Principal("O=test,L=london,C=GB"),
|
||||||
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)))
|
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)))
|
||||||
jiraClient.updateSignedRequests(requests.associateBy({ it.requestId }, { selfSignedCaCertPath }))
|
jiraClient.updateDoneCertificateSigningRequests(requests.associateBy({ it.requestId }, { selfSignedCaCertPath }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
@ -14,14 +14,12 @@ import com.nhaarman.mockito_kotlin.*
|
|||||||
import com.r3.corda.networkmanage.TestBase
|
import com.r3.corda.networkmanage.TestBase
|
||||||
import com.r3.corda.networkmanage.common.persistence.*
|
import com.r3.corda.networkmanage.common.persistence.*
|
||||||
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
||||||
import com.r3.corda.networkmanage.doorman.JiraClient
|
import com.r3.corda.networkmanage.doorman.CsrJiraClient
|
||||||
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.testing.core.ALICE_NAME
|
|
||||||
import net.corda.testing.core.BOB_NAME
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -37,7 +35,7 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
val mockitoRule: MockitoRule = MockitoJUnit.rule()
|
val mockitoRule: MockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var jiraClient: JiraClient
|
private lateinit var jiraClient: CsrJiraClient
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var certificationRequestStorage: CertificateSigningRequestStorage
|
private lateinit var certificationRequestStorage: CertificateSigningRequestStorage
|
||||||
@ -68,7 +66,7 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
fun `If jira connection fails we don't mark the ticket as created`() {
|
fun `If jira connection fails we don't mark the ticket as created`() {
|
||||||
whenever(defaultCsrHandler.saveRequest(any())).thenReturn(requestId)
|
whenever(defaultCsrHandler.saveRequest(any())).thenReturn(requestId)
|
||||||
whenever(defaultCsrHandler.getResponse(requestId)).thenReturn(certificateResponse)
|
whenever(defaultCsrHandler.getResponse(requestId)).thenReturn(certificateResponse)
|
||||||
whenever(jiraClient.createRequestTicket(eq(requestId), any())).thenThrow(IllegalStateException("something broke"))
|
whenever(jiraClient.createCertificateSigningRequestTicket(eq(requestId), any())).thenThrow(IllegalStateException("something broke"))
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
jiraCsrHandler.saveRequest(pkcS10CertificationRequest)
|
jiraCsrHandler.saveRequest(pkcS10CertificationRequest)
|
||||||
@ -91,7 +89,7 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
fun `create tickets`() {
|
fun `create tickets`() {
|
||||||
val csr = certificateSigningRequest(
|
val csr = certificateSigningRequest(
|
||||||
requestId = requestId,
|
requestId = requestId,
|
||||||
legalName = ALICE_NAME,
|
legalName = CordaX500Name.parse("O=Test Org., C=GB, L=London"),
|
||||||
status = RequestStatus.NEW,
|
status = RequestStatus.NEW,
|
||||||
request = pkcS10CertificationRequest)
|
request = pkcS10CertificationRequest)
|
||||||
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr))
|
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr))
|
||||||
@ -99,7 +97,7 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
// Test
|
// Test
|
||||||
jiraCsrHandler.processRequests()
|
jiraCsrHandler.processRequests()
|
||||||
|
|
||||||
verify(jiraClient).createRequestTicket(requestId, csr.request)
|
verify(jiraClient).createCertificateSigningRequestTicket(requestId, csr.request)
|
||||||
verify(certificationRequestStorage).markRequestTicketCreated(requestId)
|
verify(certificationRequestStorage).markRequestTicketCreated(requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,8 +105,8 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
fun `sync tickets status`() {
|
fun `sync tickets status`() {
|
||||||
val id1 = SecureHash.randomSHA256().toString()
|
val id1 = SecureHash.randomSHA256().toString()
|
||||||
val id2 = SecureHash.randomSHA256().toString()
|
val id2 = SecureHash.randomSHA256().toString()
|
||||||
val csr1 = CertificateSigningRequest(id1, ALICE_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
|
val csr1 = CertificateSigningRequest(id1, CordaX500Name.parse("O=Test1 Org., C=GB, L=London"), SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
|
||||||
val csr2 = CertificateSigningRequest(id2, BOB_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
|
val csr2 = CertificateSigningRequest(id2, CordaX500Name.parse("O=Test2 Org., C=GB, L=London"), SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
|
||||||
|
|
||||||
val requests = mutableMapOf(id1 to csr1, id2 to csr2)
|
val requests = mutableMapOf(id1 to csr1, id2 to csr2)
|
||||||
|
|
||||||
@ -135,8 +133,8 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
// Test.
|
// Test.
|
||||||
jiraCsrHandler.processRequests()
|
jiraCsrHandler.processRequests()
|
||||||
|
|
||||||
verify(jiraClient).createRequestTicket(id1, csr1.request)
|
verify(jiraClient).createCertificateSigningRequestTicket(id1, csr1.request)
|
||||||
verify(jiraClient).createRequestTicket(id2, csr2.request)
|
verify(jiraClient).createCertificateSigningRequestTicket(id2, csr2.request)
|
||||||
|
|
||||||
verify(certificationRequestStorage).markRequestTicketCreated(id1)
|
verify(certificationRequestStorage).markRequestTicketCreated(id1)
|
||||||
verify(certificationRequestStorage).markRequestTicketCreated(id2)
|
verify(certificationRequestStorage).markRequestTicketCreated(id2)
|
||||||
@ -147,7 +145,7 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
|
|
||||||
// Verify jira client get the correct call.
|
// Verify jira client get the correct call.
|
||||||
verify(jiraClient).updateRejectedRequests(listOf(id2))
|
verify(jiraClient).updateRejectedRequests(listOf(id2))
|
||||||
verify(jiraClient).updateSignedRequests(emptyMap())
|
verify(jiraClient).updateDoneCertificateSigningRequests(emptyMap())
|
||||||
|
|
||||||
// Sign request 1
|
// Sign request 1
|
||||||
val certPath = mock<CertPath>()
|
val certPath = mock<CertPath>()
|
||||||
@ -158,6 +156,6 @@ class JiraCsrHandlerTest : TestBase() {
|
|||||||
jiraCsrHandler.processRequests()
|
jiraCsrHandler.processRequests()
|
||||||
|
|
||||||
// Update signed request should be called.
|
// Update signed request should be called.
|
||||||
verify(jiraClient).updateSignedRequests(mapOf(id1 to certPath))
|
verify(jiraClient).updateDoneCertificateSigningRequests(mapOf(id1 to certPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import com.r3.corda.networkmanage.common.utils.writeObject
|
|||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.x500Name
|
import net.corda.core.internal.x500Name
|
||||||
|
import net.corda.nodeapi.internal.createDevNodeCa
|
||||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.core.freePort
|
import net.corda.testing.core.freePort
|
||||||
@ -117,7 +118,9 @@ class CertificateRevocationSocketServerTest {
|
|||||||
certificateSerialNumber = BigInteger.TEN,
|
certificateSerialNumber = BigInteger.TEN,
|
||||||
status = status,
|
status = status,
|
||||||
reason = CRLReason.KEY_COMPROMISE,
|
reason = CRLReason.KEY_COMPROMISE,
|
||||||
modifiedAt = Instant.now()
|
modifiedAt = Instant.now(),
|
||||||
|
certificateSigningRequestId = "CSR-ID",
|
||||||
|
certificate = createDevNodeCa(DEV_INTERMEDIATE_CA, CordaX500Name.parse("O=R3Cev, L=London, C=GB")).certificate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user