Onboarding Members to Dynamic Networks
This section describes how to configure a dynamic network to onboard new members. It assumes that you have configured the MGM for the network.
- Start a Corda cluster.
- Create an MGM GroupPolicy.json file.
- Package the MGM GroupPolicy.json file into an MGM CPI.
- Upload the CPI to your cluster.
- Create a virtual node in your cluster for the MGM.
- Assign required Hardware Security Modules (HSMs) for the MGM.
- Create required keys and optionally import required certificates.
- Build the registration context.
- Use the register endpoint to finalise the MGM setup so that it is ready to accept members.
- Export the GroupPolicy.json file that members require to join the group.
- Package this GroupPolicy.json file into a member CPI.
- Upload this CPI to the cluster.
- Create the virtual node for the member.
- Assign the required HSMs for P2P session initiation.
- Assign the required HSMs for the ledger.
- Create the required keys, and optionally import required certificates.
- Configure the member virtual node for network communication.
- Build the registration context.
- Use the register endpoint to request membership from the MGM.
Set Variables
Set the values of variables for use in later commands:
Set the P2P gateway host and port and the REST API host and port. This may also vary depending on where you have deployed your cluster(s) and how you have forwarded the ports.
export REST_HOST=localhost export REST_PORT=8888 export P2P_GATEWAY_HOST=localhost export P2P_GATEWAY_PORT=8080
$REST_HOST = "localhost" $REST_PORT = 8888 $P2P_GATEWAY_HOST = "localhost" $P2P_GATEWAY_PORT = 8080 $AUTH_INFO = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("admin:admin" -f $username,$password)))
These values vary depending on where you have deployed your cluster(s) and how you have forwarded the ports. For example, if
corda-p2p-gateway-worker
is the name of the P2P gateway Kubernetes service andcorda-cluster-a
is the namespace that the Corda cluster is deployed within, set$P2P_GATEWAY_HOST
tocorda-p2p-gateway-worker.corda-cluster-a
. Alternatively, you can specify the IP address of the gateway, instead of the hostname. For example,192.168.0.1
.Set the REST API URL. This may vary depending on where you have deployed your cluster(s) and how you have forwarded the ports.
export API_URL="https://$REST_HOST:$REST_PORT/api/v1"
$API_URL = "https://$REST_HOST:$REST_PORT/api/v1"
Set the working directory for storing temporary files.
export WORK_DIR=~/Desktop/register-member mkdir -p "$WORK_DIR"
$WORK_DIR = "$HOME/register-member" md $WORK_DIR -Force
Generate the Group Policy File
To onboard members to a group, a running MGM is required. To join, members must use a group policy file exported from the MGM of that group.
To retrieve the GroupPolicy.json
file from the MGM:
Set the MGM properties, by running these commands:
If using Bash, create theexport MGM_REST_HOST=localhost export MGM_REST_PORT=8888 export MGM_API_URL="https://$MGM_REST_HOST:$MGM_REST_PORT/api/v1" export MGM_HOLDING_ID=<MGM Holding ID>
$MGM_REST_HOST = "localhost" $MGM_REST_PORT = "8888" $MGM_API_URL = "https://$MGM_REST_HOST:$MGM_REST_PORT/api/v1" $MGM_HOLDING_ID = <MGM Holding ID> Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$MGM_API_URL/mgm/$MGM_HOLDING_ID/info" | ConvertTo-Json -Depth 4 > $WORK_DIR/GroupPolicy.json
GroupPolicy.json
by exporting it using the MGM, by running this command:curl --insecure -u admin:admin -X GET $MGM_API_URL/mgm/$MGM_HOLDING_ID/info > "$WORK_DIR/GroupPolicy.json"
Create a CPI
Build a CPI using the Corda CLI packaging plugin, passing in the member CPB and your generated GroupPolicy.json
file. For more information about creating CPIs, see the CorDapp Packaging section.
Upload the CPI
To upload the CPI, run the following:
export CPI_PATH=<CPI-directory/CPI-filename.cpi>
curl --insecure -u admin:admin -F upload=@$CPI_PATH $API_URL/cpi/
$CPI_PATH = "$WORK_DIR\mgm-5.0.0.0-SNAPSHOT-package.cpi"
$CPI_UPLOAD_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/cpi/" -Method Post -Form @{
upload = Get-Item -Path $CPI_PATH
}
The returned identifier (for example f0a0f381-e0d6-49d2-abba-6094992cef02
) is the CPI ID
.
Use this identifier to get the checksum of the CPI:
export CPI_ID=<CPI ID>
curl --insecure -u admin:admin $API_URL/cpi/status/$CPI_ID
$CPI_ID = $CPI_UPLOAD_RESPONSE.id
$CPI_STATUS_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/cpi/status/$CPI_ID"
The result contains the cpiFileChecksum
. Save this for the next step.
Create a Virtual Node
To create a virtual node for the member, run the following commands, changing the X.500 name:
export CPI_CHECKSUM=<CPI checksum>
export X500_NAME="C=GB, L=London, O=Alice"
curl --insecure -u admin:admin -d '{"request": {"cpiFileChecksum": "'$CPI_CHECKSUM'", "x500Name": "'$X500_NAME'"}}' $API_URL/virtualnode
$X500_NAME = "C=GB, L=London, O=Alice"
$VIRTUAL_NODE_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/virtualnode" -Method Post -Body (ConvertTo-Json @{
request = @{
cpiFileChecksum = $CPI_STATUS_RESPONSE.cpiFileChecksum
x500Name = $X500_NAME
}
})
$HOLDING_ID = $VIRTUAL_NODE_RESPONSE.holdingIdentity.shortHash
If using Bash, run the following, replacing <holding identity ID>
with the ID returned in holdingIdentity.shortHash
(for example, 58B6030FABDD
).
export HOLDING_ID=<holding identity ID>
Configure Key Pairs and Certificates
P2P Session Initiation Key Pair and Certificate
To assign a soft high security module (HSM) and generate a session initiation key pair:
curl --insecure -u admin:admin -X POST $API_URL/hsm/soft/$HOLDING_ID/SESSION_INIT
curl --insecure -u admin:admin -X POST $API_URL'/keys/'$HOLDING_ID'/alias/'$HOLDING_ID'-session/category/SESSION_INIT/scheme/CORDA.ECDSA.SECP256R1'
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/hsm/soft/$HOLDING_ID/SESSION_INIT"
$SESSION_KEY_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/keys/$HOLDING_ID/alias/$HOLDING_ID-session/category/SESSION_INIT/scheme/CORDA.ECDSA.SECP256R1"
$SESSION_KEY_ID = $SESSION_KEY_RESPONSE.id
If using Bash, the result contains session key ID
(for example, 3B9A266F96E2). Run the following command to save this ID for use in subsequent steps:
export SESSION_KEY_ID=<session key ID>
Ledger Key Pair and Certificate
To assign a soft high security module (HSM) and generate a ledger key pair:
curl --insecure -u admin:admin -X POST $API_URL/hsm/soft/$HOLDING_ID/LEDGER
curl --insecure -u admin:admin -X POST $API_URL/keys/$HOLDING_ID/alias/$HOLDING_ID-ledger/category/LEDGER/scheme/CORDA.ECDSA.SECP256R1
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/hsm/soft/$HOLDING_ID/LEDGER"
$LEDGER_KEY_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/keys/$HOLDING_ID/alias/$HOLDING_ID-ledger/category/LEDGER/scheme/CORDA.ECDSA.SECP256R1"
$LEDGER_KEY_ID = $LEDGER_KEY_RESPONSE.id
If using Bash, the result contains the ledger key ID (for example, 3B9A266F96E2). Run the following command to save this ID for use in subsequent steps:
export LEDGER_KEY_ID=<ledger key ID>
Notary Key Pair
Generate notary keys in a similar way as done for other key types. First, create a HSM, then generate the key and store the ID:
curl --insecure -u admin:admin -X POST $API_URL/hsm/soft/$HOLDING_ID/NOTARY
curl --insecure -u admin:admin -X POST $API_URL/keys/$HOLDING_ID/alias/$HOLDING_ID-notary/category/NOTARY/scheme/CORDA.ECDSA.SECP256R1
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/hsm/soft/$HOLDING_ID/NOTARY"
$LEDGER_KEY_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/keys/$HOLDING_ID/alias/$HOLDING_ID-notary/category/NOTARY/scheme/CORDA.ECDSA.SECP256R1"
$NOTARY_KEY_ID = $NOTARY_KEY_RESPONSE.id
If using Bash, the result contains the notary key ID (for example, 3B9A266F96E2). Run the following command to save this ID for use in subsequent steps:
export NOTARY_KEY_ID=<notary key ID>
TLS Key Pair and Certificate
You must perform the same steps that you did for setting up the MGM to enable P2P communication for the locally hosted identities. Use the Certificate Authority (CA) whose trustroot certificate was configured in the MGM’s registration context.
If using mutual TLS, you must must add the certificate subject to the allowed list of the MGM. For more information, see Update the MGM Allowed Certificate Subject List.
Create a TLS key pair at the P2P cluster-level by running this command:
curl -k -u admin:admin -X POST -H "Content-Type: application/json" $API_URL/keys/p2p/alias/p2p-TLS/category/TLS/scheme/CORDA.RSA
$TLS_KEY_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/keys/p2p/alias/p2p-TLS/category/TLS/scheme/CORDA.RSA" $TLS_KEY_ID = $TLS_KEY_RESPONSE.id
If using Bash, the endpoint returns the TLS key ID:
export TLS_KEY_ID=<TLS-key-ID>
Create a certificate for the TLS key pair by running the following command to generate a certificate signing request (CSR):
curl -k -u admin:admin -X POST -H "Content-Type: application/json" -d '{"x500Name": "CN=CordaOperator, C=GB, L=London, O=Org", "subjectAlternativeNames": ["'$P2P_GATEWAY_HOST'"]}' $API_URL/certificates/p2p/$TLS_KEY_ID > "$WORK_DIR"/request2.csr
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/certificates/p2p/$TLS_KEY_ID" -Body (ConvertTo-Json @{ x500Name = "CN=CordaOperator, C=GB, L=London" subjectAlternativeNames = @($P2P_GATEWAY_HOST) }) > $WORK_DIR/request2.csr
You can inspect the
request2.csr
file by running this command:openssl req -text -noout -verify -in ./request.csr
The contents should resemble the following:
-----BEGIN CERTIFICATE REQUEST----- MIIDkjCCAfwCAQAwLjELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEOMAwG A1UEAxMFQWxpY2UwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQChJ9CW 9bpnlKOg0OkRRSEPuo2CBMY4r9hDqgLPiadHqBh+QcXqZv23ftHDgFl7CnidG9xc 1t8oCcOYSd9SfLTuxINF+eUoBZ5n4Igj210z1kp20bGc31qi7chzNHiLgpDXrh0x 5AItxnHV+/grD4y3FxOxA6M0rGjMHsVWrxytTchEVN1cCEwUXWG0sjO3y4loctln nBAQyjxo0au1K5r9jzjz8dorkgCALYuSpr4eyjRij+/DuStH9VHcz+XdFz9MlW/B k4c8VzpqGhXs8w5UagnB81ZOmm+xoQOrELeZ1RPAEqCV8kAOO0xjfpTUIzOqz5bC U0luWEM0Gw0BsNdrdaQtrpAs0mv3Rmq6pMD6jlXTqJ8NbravrAnP1DMqHZKKMWnU PEyAuDJB3ndukJfRA03UpRmonvus1UXvheUgRG2f0RIbefvBLUqt+MBucgkUNQmf qyPuD2XaCcOLfSZ+FTtMg2P2E4l2cAnhmzeGL3kHTvilm3ZWlWBUEfJ4BZ0CAwEA AaAhMB8GCSqGSIb3DQEJDjESMBAwDgYDVR0PAQH/BAQDAgeAMAsGCSqGSIb3DQEB DQOCAYEAYtt2Fpgelb81jAEDayLHKISuwRXThGQv3xuZtwxdiC3gWdJbV9H6IWzv cmlxXUrnaju30eQ/LkB8tzuy++p2fIctO8y8Hiw743hddy6dEd21PxQHsNTAS5Ko 1yikmzzRwT5JwMY+EZDxDxXfYViq0xaZoHPbcr3LmwkipqRnZo4e2i5jUCQjFMYq ZThLbl1NKHR98O2/akekmlpuGgtLFOLlSHYkZvZY7K1IEkLZAdbo4fhimDDxQg7T v59nY3SSGbNirFlqz4UfAjKpPyV+UVgRNcFNxJyA6/eL/J8Wedb3zqat2utilLb7 6nicgQ0S3Xb5gPsTUXcsHRuD+FVJG+eJ1qEvh2srIZ57Nnjr9FTy6mqqN4Ln3g31 k9GLOv2kll+tFWjAZEDSRX2VqxkVOlEuKeGXcdrJ2EXz3G444A0wtiTgppwdy9Az YCOEnQMQQUE3gXBax1UsQwl7M71it1/QuhtsBccLfX6rB8BNldwRibADPD16Y6PY LwkiaZXc -----END CERTIFICATE REQUEST-----
Provide the chosen CA with this CSR and request for certificate issuance.
To upload the certificate chain to the Corda cluster, run this command, where
certificate-folder
is the path to the folder in which you saved the certificate received from the CA:curl -k -u admin:admin -X PUT -F certificate=<certificate-folder>/certificate.pem -F alias=p2p-tls-cert $API_URL/certificates/cluster/p2p-tls
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Put -Uri "$API_URL/certificates/cluster/p2p-tls" -Form @{ certificate = Get-Item -Path <certificate-folder>\certificate.pem alias = "p2p-tls-cert" }
You can optionally omit the root certificate.
If you upload a certificate chain consisting of more than one certificate, ensure that-----END CERTIFICATE-----
and-----BEGIN CERTIFICATE-----
from the next certificate are separated by a new line with no empty spaces in between.
Disable Revocation Checks
If the CA has not been configured with revocation (for example, via CRL or OCSP), you can disable revocation checks. By default, revocation checks are enabled. This only needs to be done once per cluster.
Disable Revocation Checks Using Bash
If using Bash, to disable revocation checks, do the following:
- Retrieve the current gateway configuration version:
curl --insecure -u admin:admin -X GET $API_URL/config/corda.p2p.gateway
- Save the displayed version number from the response as a variable:
export CONFIG_VERSION=<configuration-version>
- Send the following request to disable revocation checks for the specified gateway worker:
curl -k -u admin:admin -X PUT -d '{"section":"corda.p2p.gateway", "version":"'$CONFIG_VERSION'", "config":"{ \"sslConfig\": { \"revocationCheck\": { \"mode\": \"OFF\" } } }", "schemaVersion": {"major": 1, "minor": 0}}' $API_URL"/config"
Disable Revocation Checks Using PowerShell
If using PowerShell, to disable revocation checks, run the following:
$CONFIG_VERSION = (Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/config/corda.p2p.gateway").version
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Put -Uri "$API_URL/config" -Body (ConvertTo-Json -Depth 4 @{
section = "corda.p2p.gateway"
version = $CONFIG_VERSION
config = @{
sslConfig = @{
revocationCheck = @{
mode = "OFF"
}
}
}
schemaVersion = @{
major = 1
minor = 0
}
})
Configure the Member Virtual Node for Network Communication
You must configure the virtual node as a network participant with the properties required for P2P messaging. The order is slightly different to MGM onboarding because for members, you must perform this step before registering. To configure the member virtual node, run this command:
curl -k -u admin:admin -X PUT -d '{"p2pTlsCertificateChainAlias": "p2p-tls-cert", "useClusterLevelTlsCertificateAndKey": true, "sessionKeyId": "'$SESSION_KEY_ID'"}' $API_URL/network/setup/$HOLDING_ID
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/network/setup/$HOLDING_ID" -Method Put -Body (ConvertTo-Json @{
p2pTlsCertificateChainAlias = "p2p-tls-cert"
useClusterLevelTlsCertificateAndKey = $true
sessionKeyId = $SESSION_KEY_ID
})
p2pTlsCertificateChainAlias
— the alias used when importing the TLS certificate.useClusterLevelTlsCertificateAndKey
- true if the TLS certificate and key are cluster-level certificates and keyssessionKeyId
— the session key ID previously generated.
Build Registration Context
KeysRpcOps
. One of them is used as an example below.To build the registration context, run the following command, replacing the endpoint URL with the endpoint of the P2P gateway.
If you are testing in a single cluster, this value isn’t important, so you can use something like https://localhost:8080
.
If the network is a multi-cluster environment, the URL must be a valid P2P gateway URL.
For example, https://corda-p2p-gateway-worker.corda-cluster-a:8080
, where corda-p2p-gateway-worker
is the name of the P2P gateway Kubernetes service and corda-cluster-a
is the namespace that the Corda cluster is deployed within.
export REGISTRATION_CONTEXT='{
"corda.session.key.id": "'$SESSION_KEY_ID'",
"corda.session.key.signature.spec": "SHA256withECDSA",
"corda.ledger.keys.0.id": "'$LEDGER_KEY_ID'",
"corda.ledger.keys.0.signature.spec": "SHA256withECDSA",
"corda.endpoints.0.connectionURL": "https://'$P2P_GATEWAY_HOST':'$P2P_GATEWAY_PORT'",
"corda.endpoints.0.protocolVersion": "1"
}'
$REGISTRATION_CONTEXT = @{
'corda.session.key.id' = $SESSION_KEY_ID
'corda.session.key.signature.spec' = "SHA256withECDSA"
'corda.ledger.keys.0.id' = $LEDGER_KEY_ID
'corda.ledger.keys.0.signature.spec' = "SHA256withECDSA"
'corda.endpoints.0.connectionURL' = "https://$P2P_GATEWAY_HOST:$P2P_GATEWAY_PORT"
'corda.endpoints.0.protocolVersion' = "1"
}
If you are registering a member as a notary service representative, run the following command to build the registration context:
export REGISTRATION_CONTEXT='{
"corda.session.key.id": "'$SESSION_KEY_ID'",
"corda.session.key.signature.spec": "SHA256withECDSA",
"corda.ledger.keys.0.id": "'$LEDGER_KEY_ID'",
"corda.ledger.keys.0.signature.spec": "SHA256withECDSA",
"corda.notary.keys.0.id" = "$NOTARY_KEY_ID",
"corda.notary.keys.0.signature.spec" = "SHA256withECDSA"
"corda.endpoints.0.connectionURL": "https://'$P2P_GATEWAY_HOST':'$P2P_GATEWAY_PORT'",
"corda.endpoints.0.protocolVersion": "1",
"corda.roles.0" : "notary",
"corda.notary.service.name" : <An X500 name for the notary service>,
"corda.notary.service.plugin" : "net.corda.notary.NonValidatingNotary"
}'
$REGISTRATION_CONTEXT = @{
'corda.session.key.id' = $SESSION_KEY_ID
'corda.session.key.signature.spec' = "SHA256withECDSA"
'corda.ledger.keys.0.id' = $LEDGER_KEY_ID
'corda.ledger.keys.0.signature.spec' = "SHA256withECDSA"
"corda.notary.keys.0.id" = "$NOTARY_KEY_ID",
"corda.notary.keys.0.signature.spec" = "SHA256withECDSA"
'corda.endpoints.0.connectionURL' = "https://$P2P_GATEWAY_HOST`:$P2P_GATEWAY_PORT"
'corda.endpoints.0.protocolVersion' = "1"
'corda.roles.0' : "notary",
'corda.notary.service.name' : <An X500 name for the notary service>,
'corda.notary.service.plugin' : "net.corda.notary.NonValidatingNotary"
}
Register Members
To register a member, run the following command:
curl --insecure -u admin:admin -d '{ "memberRegistrationRequest": { "action": "requestJoin", "context": '$REGISTRATION_CONTEXT' } }' $API_URL/membership/$HOLDING_ID
$REGISTER_RESPONSE = Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Method Post -Uri "$API_URL/membership/$HOLDING_ID" -Body (ConvertTo-Json -Depth 4 @{
memberRegistrationRequest = @{
action = "requestJoin"
context = $REGISTRATION_CONTEXT
}
})
$REGISTER_RESPONSE.registrationStatus
This sends a join request to the MGM, the response should be SUBMITTED
.
If you are using the Swagger UI, use this example:
{
"memberRegistrationRequest":{
"action":"requestJoin",
"context": <registration context>
}
}
You can confirm if the member was onboarded successfully by checking the status of the registration request:
export REGISTRATION_ID=<registration ID>
curl --insecure -u admin:admin -X GET $API_URL/membership/$HOLDING_ID/$REGISTRATION_ID
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/membership/$HOLDING_ID/${REGISTER_RESPONSE.registrationId}"
If successful, you should see the APPROVED
registration status.
After registration, you can use the look-up functions provided by the MemberLookupRpcOps
to confirm that your member can see other members and has ACTIVE
membership status:
curl --insecure -u admin:admin -X GET $API_URL/members/$HOLDING_ID
Invoke-RestMethod -SkipCertificateCheck -Headers @{Authorization=("Basic {0}" -f $AUTH_INFO)} -Uri "$API_URL/membership/$HOLDING_ID" | ConvertTo-Json -Depth 4
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.