From 3bee9e89dad02d3452afa4e53ee818d0f18d8bd5 Mon Sep 17 00:00:00 2001 From: Rafael Belchior Date: Fri, 15 Nov 2024 19:47:51 +0200 Subject: [PATCH] feat(cactus-connector-fabric): add get tx receipt by tx id Authored-by: Eduardo Vasques Signed-off-by: Rafael Belchior --- .github/dast/ generate-jwt.js | 36 +++++++ .../.dast-nuclei-cmd-api-server.yaml | 99 +++++++++++++------ .github/workflows/generate-jwt.js | 36 +++++++ .../get-transaction-receipt-by-tx-id.ts | 2 +- .../plugin-ledger-connector-fabric.ts | 42 +++++++- .../src/main/typescript/public-api.ts | 1 + tools/generate-jwt.js | 36 +++++++ 7 files changed, 215 insertions(+), 37 deletions(-) create mode 100644 .github/dast/ generate-jwt.js create mode 100644 .github/workflows/generate-jwt.js create mode 100644 tools/generate-jwt.js diff --git a/.github/dast/ generate-jwt.js b/.github/dast/ generate-jwt.js new file mode 100644 index 0000000000..efc644f696 --- /dev/null +++ b/.github/dast/ generate-jwt.js @@ -0,0 +1,36 @@ +const fs = require("fs"); +const { SignJWT } = require("jose"); +const crypto = require("crypto"); + +// Path to the config file +const configFilePath = "./.config.json"; + +// Load and parse the config file +const config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); + +// Extract audience and issuer from the config +const audience = config.authorizationConfigJson.expressJwtOptions.audience; +const issuer = config.authorizationConfigJson.expressJwtOptions.issuer; +const secret = config.authorizationConfigJson.expressJwtOptions.secret; + +// Log audience and issuer +console.log(`Audience: ${audience}`); +console.log(`Issuer: ${issuer}`); + +// Generate a symmetric key for signing (HS256 uses a shared secret, not a public/private key pair) +const jwtKeyPair = { + privateKey: crypto.createSecretKey(secret), +}; + +// Example of how to generate a JWT using `audience`, `issuer`, and `HS256` +async function generateJWT() { + const jwt = await new SignJWT({ scope: "read:health" }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuer(issuer) + .setAudience(audience) + .sign(jwtKeyPair.privateKey); + + console.log(`Generated JWT: ${jwt}`); +} + +generateJWT(); \ No newline at end of file diff --git a/.github/workflows/.dast-nuclei-cmd-api-server.yaml b/.github/workflows/.dast-nuclei-cmd-api-server.yaml index af49840f21..db33ffb6a7 100644 --- a/.github/workflows/.dast-nuclei-cmd-api-server.yaml +++ b/.github/workflows/.dast-nuclei-cmd-api-server.yaml @@ -1,15 +1,15 @@ name: DAST_Scan_Nuclei - + env: NODEJS_VERSION: v18.18.2 - + on: push: branches: [main, dev] - + pull_request: branches: [main, dev] - + jobs: nuclei-scan: runs-on: ubuntu-22.04 @@ -25,30 +25,30 @@ jobs: libvcx \ indy-cli \ && sudo rm -f /etc/apt/sources.list.d/sovrin.list* - + - name: Set up NodeJS ${{ env.NODEJS_VERSION }} uses: actions/setup-node@v4.0.3 with: node-version: ${{ env.NODEJS_VERSION }} - + - name: Install jq run: sudo apt update && sudo apt install -y jq - + - name: Verify jq run: jq --version - + - uses: actions/checkout@v4.1.7 - + - uses: actions/setup-go@v4.0.0 with: go-version: 1.23 - + - run: go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@v3.3.5 - + - run: nuclei --version - + - run: npm run configure - + - name: Create URLs file for Nuclei run: | echo https://localhost:4000/ > urls.txt @@ -73,58 +73,93 @@ jobs: echo https://localhost:4000/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-prometheus-exporter-metrics echo https://localhost:4000/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/get-besu-record } >> urls.txt - + - run: yarn generate-api-server-config - - - run: jq '.authorizationProtocol = "NONE"' .config.json > .config2.json && mv .config2.json .config.json - + + #- run: jq '.authorizationProtocol = "NONE"' .config.json > .config2.json && mv .config2.json .config.json + # Delete the first and the second items in the array (remove keychain and manual consortium plugins) - run: jq 'del(.plugins[0,1])' .config.json > .config2.json && mv .config2.json .config.json - + - name: Install Keychain manual plugin into the API server run: jq '.plugins += [{ "packageName":"@hyperledger/cactus-plugin-keychain-memory","type":"org.hyperledger.cactus.plugin_import_type.LOCAL","action":"org.hyperledger.cactus.plugin_import_action.INSTALL","options":{"packageSrc":"/home/runner/work/cacti/cacti/packages/cactus-plugin-keychain-memory/","instanceId":"0daacd05-d1cd-4eab-9332-4ad1aff4b909","keychainId":"d29d728e-eaa0-4e2d-b187-d132242b0d9a"}}]' .config.json > .config2.json && mv .config2.json .config.json - + - name: Install Fabric connector into the API server run: jq '.plugins += [{ "packageName":"@hyperledger/cactus-plugin-ledger-connector-fabric", "type":"org.hyperledger.cactus.plugin_import_type.LOCAL", "action":"org.hyperledger.cactus.plugin_import_action.INSTALL", "options":{ "packageSrc":"/home/runner/work/cacti/cacti/packages/cactus-plugin-ledger-connector-fabric/", "instanceId":"some-unique-fabric-connector-instance-id", "peerBinary":"/fabric-samples/bin/peer", "connectionProfile":"{}", "dockerBinary":"usr/local/bin/docker","cliContainerEnv":{"CORE_PEER_LOCALMSPID":"Org1MSP","CORE_PEER_ADDRESS":"peer0.org1.example.com:7051","CORE_PEER_MSPCONFIGPATH":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp","CORE_PEER_TLS_ROOTCERT_FILE":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt","ORDERER_TLS_ROOTCERT_FILE":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem"},"discoveryOptions":{"enabled":true,"asLocalhost":true}}}] ' .config.json > .config2.json && mv .config2.json .config.json - + - name: Install Besu connector into the API server run: jq '.plugins += [{"packageName":"@hyperledger/cactus-plugin-ledger-connector-besu","type":"org.hyperledger.cactus.plugin_import_type.LOCAL","action":"org.hyperledger.cactus.plugin_import_action.INSTALL","options":{"packageSrc":"/home/runner/work/cacti/cacti/packages/cactus-plugin-ledger-connector-besu/", "rpcApiHttpHost":"http://127.0.0.1:8545", "rpcApiWsHost":"ws://127.0.0.1:8546", "instanceId":"some-unique-besu-connector-instance-id"}}]' .config.json > .config2.json && mv .config2.json .config.json - + - name: Run Besu all-in-one image run: | docker run -d -p 0.0.0.0:8545:8545/tcp -p 0.0.0.0:8546:8546/tcp -p 0.0.0.0:8888:8888/tcp -p 0.0.0.0:9001:9001/tcp -p 0.0.0.0:9545:9545/tcp ghcr.io/hyperledger/cactus-besu-all-in-one:v2.0.0-rc.7 until curl --fail -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' localhost:8545; do sleep 5; done - + - name: Print API Server Config File - ./.config.json run: cat .config.json - + - name: Print Nuclei Config File - ./.nuclei-config.yaml run: cat .nuclei-config.yaml - + - name: Print Nuclei URL List File - ./urls.txt run: cat urls.txt + #------------ + - name: Generate Audience and Issuer + id: generate_ids + run: | + echo "audience=$(uuidgen)" >> $GITHUB_ENV + echo "issuer=$(uuidgen)" >> $GITHUB_ENV + + - name: Generate RSA Keys + run: | + openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 + openssl rsa -in private_key.pem -pubout -out public_key.pem + + - name: Display Results + run: | + echo "Audience: ${{ env.audience }}" + echo "Issuer: ${{ env.issuer }}" + echo "Public Key:" + cat public_key.pem + echo "Private Key:" + cat private_key.pem + + - run: jq '.expressJwtOptions.secret = "-----BEGIN PUBLIC KEY-----\n$(cat public_key.pem)\n-----END PUBLIC KEY-----" | + .expressJwtOptions.algorithms = ["RS256"] | + .expressJwtOptions.issuer = "${{ env.issuer }}" | + .expressJwtOptions.audience = "${{ env.audience }}"' .config.json > .config2.json && mv .config2.json .config.json + + - name: Generate Auth Bearer Token + run: | + HEADER_B64=$(echo '{"alg":"RS256"}' | openssl base64 -e -A | tr -d '=' | tr '/+' '_-') + PAYLOAD_B64=$(cat '{"scope":"read:health","iss":"${{ env.issuer }}","aud":"${{ env.audience }}"}' | openssl base64 -e -A | tr -d '=' | tr '/+' '_-') + + SIGNATURE=$(echo -n "$HEADER_B64.$PAYLOAD_B64" | openssl dgst -sha256 -sign cat private_key.pem | openssl base64 -e -A | tr -d '=' | tr '/+' '_-') + JWT="$HEADER_B64.$PAYLOAD_B64.$SIGNATURE" + echo dast_jwt=$JWT >> $GITHUB_ENV - name: Start API Server & Run DAST uses: BerniWittmann/background-server-action@v1.1.0 env: - # Needed because the wait-on syntax otherwise keeps thinking that - # there is a problem due to our self signed certificates on the - # test instance of the API server - NODE_TLS_REJECT_UNAUTHORIZED: 0 + # Needed because the wait-on syntax otherwise keeps thinking that + # there is a problem due to our self signed certificates on the + # test instance of the API server + NODE_TLS_REJECT_UNAUTHORIZED: 0 with: build: yarn --version start: yarn start:api-server command: "nuclei -version" command-windows: echo "The project build is not supported on the Windows operating system. Please use Linux or macOS" - wait-on: "https://localhost:4000/api/v1/api-server/healthcheck" # wait for 10 minutes for the server to respond wait-on-timeout: 120 - + wait-on-command: | + curl -X GET https://localhost:4000/api/v1/api-server/healthcheck -k -H "Authorization: Bearer ${{ env.dast_jwt }}" + - name: Run the dast nuclei scan run: "nuclei -list=urls.txt -dast -severity=high,critical -sarif-export ~/nuclei.sarif -output=nuclei.log" - + - name: GitHub Workflow artifacts uses: actions/upload-artifact@v3.0.0 with: name: nuclei.log - path: nuclei.log + path: nuclei.log \ No newline at end of file diff --git a/.github/workflows/generate-jwt.js b/.github/workflows/generate-jwt.js new file mode 100644 index 0000000000..7ddcb6a5a0 --- /dev/null +++ b/.github/workflows/generate-jwt.js @@ -0,0 +1,36 @@ +const fs = require("fs"); +import { SignJWT, exportSPKI, generateKeyPair } from "jose"; +const crypto = require("crypto"); + +// Path to the config file +const configFilePath = "./.config.json"; + +// Load and parse the config file +const config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); + +// Extract audience and issuer from the config +const audience = config.authorizationConfigJson.expressJwtOptions.audience; +const issuer = config.authorizationConfigJson.expressJwtOptions.issuer; +const secret = config.authorizationConfigJson.expressJwtOptions.secret; + +// Log audience and issuer +console.log(`Audience: ${audience}`); +console.log(`Issuer: ${issuer}`); + +// Generate a symmetric key for signing (HS256 uses a shared secret, not a public/private key pair) +const jwtKeyPair = { + privateKey: crypto.createSecretKey(secret), +}; + +// Example of how to generate a JWT using `audience`, `issuer`, and `HS256` +async function generateJWT() { + const jwt = await new SignJWT({ scope: "read:health" }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuer(issuer) + .setAudience(audience) + .sign(jwtKeyPair.privateKey); + + console.log(`Generated JWT: ${jwt}`); +} + +generateJWT(); \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts index b874bc0e77..887a8f4284 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/get-transaction-receipt-by-tx-id.ts @@ -157,7 +157,7 @@ export async function getTransactionReceiptByTxID( if (!extensionNsRwset.rwset) continue; const rwset = extensionNsRwset.rwset; - if (!rwset.writes) continue; + if (!rwset.writes || rwset.writes.length === 0) continue; const rwsetWrite = rwset.writes; if (!rwsetWrite[0].key) continue; const rwsetKey = rwsetWrite[0].key; diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts index fa785261b8..10fcc1ba1a 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts @@ -147,6 +147,7 @@ import { formatCactiFullBlockResponse, formatCactiTransactionsBlockResponse, } from "./get-block/cacti-block-formatters"; + import { GetBlockEndpointV1 } from "./get-block/get-block-endpoint-v1"; import { GetChainInfoEndpointV1 } from "./get-chain-info/get-chain-info-endpoint-v1"; import { querySystemChainCode } from "./common/query-system-chain-code"; @@ -158,10 +159,17 @@ import { } from "./common/utils"; import { findAndReplaceFabricLoggingSpec } from "./common/find-and-replace-fabric-logging-spec"; import { deployContractGoSourceImplFabricV256 } from "./deploy-contract-go-source/deploy-contract-go-source-impl-fabric-v2-5-6"; +import { Observable, ReplaySubject } from "rxjs"; const { loadFromConfig } = require("fabric-network/lib/impl/ccp/networkconfig"); assertFabricFunctionIsAvailable(loadFromConfig, "loadFromConfig"); +export interface IRunTxReqWithTxId { + request: RunTransactionRequest; + transactionId: string; + timestamp: Date; +} + /** * Constant value holding the default $GOPATH in the Fabric CLI container as * observed on fabric deployments that are produced by the official examples @@ -229,6 +237,7 @@ export class PluginLedgerConnectorFabric private readonly certStore: CertDatastore; private readonly sshDebugOn: boolean; private runningWatchBlocksMonitors = new Set(); + private txSubject: ReplaySubject = new ReplaySubject(); public get className(): string { return PluginLedgerConnectorFabric.CLASS_NAME; @@ -295,18 +304,26 @@ export class PluginLedgerConnectorFabric ); } + this.sshDebugOn = opts.sshDebugOn === true; if (this.opts.sshConfig) { this.sshConfig = this.opts.sshConfig; + + if (this.sshDebugOn) { + this.sshConfig = this.enableSshDebugLogs(this.sshConfig); + } } else if (this.opts.sshConfigB64) { const sshConfigBuffer = Buffer.from(this.opts.sshConfigB64, "base64"); const sshConfigString = sshConfigBuffer.toString("utf-8"); this.sshConfig = JSON.parse(sshConfigString); + + if (this.sshDebugOn) { + this.sshConfig = this.enableSshDebugLogs(this.sshConfig); + } } else { - throw new Error("Cannot instantiate Fabric connector without SSH config"); - } - if (this.sshDebugOn) { - this.sshConfig = this.enableSshDebugLogs(this.sshConfig); + // throw new Error("Cannot instantiate Fabric connector without SSH config"); + this.sshConfig = {} + console.log("The check for sshConfig has been temporarily removed") } this.signCallback = opts.signCallback; @@ -339,6 +356,10 @@ export class PluginLedgerConnectorFabric return `@hyperledger/cactus-plugin-ledger-connector-fabric`; } + public getTxSubjectObservable(): Observable { + return this.txSubject.asObservable(); + } + public async onPluginInit(): Promise { return; } @@ -1178,6 +1199,7 @@ export class PluginLedgerConnectorFabric ): Promise { const fnTag = `${this.className}#transact()`; this.log.debug("%s ENTER", fnTag); + const { channelName, contractName, @@ -1247,6 +1269,7 @@ export class PluginLedgerConnectorFabric const transactionProposal = await contract.createTransaction(fnName); transactionProposal.setEndorsingPeers(endorsingTargets); out = await transactionProposal.setTransient(transientMap).submit(); + transactionId = transactionProposal.getTransactionId(); break; } default: { @@ -1255,6 +1278,17 @@ export class PluginLedgerConnectorFabric } } + // create IRunTxReqWithTxId for transaction monitoring + const receiptData: IRunTxReqWithTxId = { + request: req, + transactionId: transactionId == "" ? uuidv4() : transactionId, + timestamp: new Date(), + }; + this.log.debug( + `IRunTxReqWithTxId created with ID: ${receiptData.transactionId}`, + ); + this.txSubject.next(receiptData); + const res: RunTransactionResponse = { functionOutput: this.convertToTransactionResponseType( out, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts index 50172d9cf8..d7d62d46a6 100755 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts @@ -9,6 +9,7 @@ export { PluginLedgerConnectorFabric, IPluginLedgerConnectorFabricOptions, SignPayloadCallback, + IRunTxReqWithTxId, } from "./plugin-ledger-connector-fabric"; import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; diff --git a/tools/generate-jwt.js b/tools/generate-jwt.js new file mode 100644 index 0000000000..7ddcb6a5a0 --- /dev/null +++ b/tools/generate-jwt.js @@ -0,0 +1,36 @@ +const fs = require("fs"); +import { SignJWT, exportSPKI, generateKeyPair } from "jose"; +const crypto = require("crypto"); + +// Path to the config file +const configFilePath = "./.config.json"; + +// Load and parse the config file +const config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); + +// Extract audience and issuer from the config +const audience = config.authorizationConfigJson.expressJwtOptions.audience; +const issuer = config.authorizationConfigJson.expressJwtOptions.issuer; +const secret = config.authorizationConfigJson.expressJwtOptions.secret; + +// Log audience and issuer +console.log(`Audience: ${audience}`); +console.log(`Issuer: ${issuer}`); + +// Generate a symmetric key for signing (HS256 uses a shared secret, not a public/private key pair) +const jwtKeyPair = { + privateKey: crypto.createSecretKey(secret), +}; + +// Example of how to generate a JWT using `audience`, `issuer`, and `HS256` +async function generateJWT() { + const jwt = await new SignJWT({ scope: "read:health" }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuer(issuer) + .setAudience(audience) + .sign(jwtKeyPair.privateKey); + + console.log(`Generated JWT: ${jwt}`); +} + +generateJWT(); \ No newline at end of file