@ethernity-cloud/sdk-js 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +29 -0
- package/init.js +1 -1
- package/nodenithy/build/securelock/Dockerfile.tmpl +21 -5
- package/nodenithy/build.mjs +42 -5
- package/nodenithy/ipfs.mjs +340 -426
- package/nodenithy/run/docker-compose-final.yml.tmpl +3 -0
- package/nodenithy/run/docker-compose.yml.tmpl +10 -8
- package/nodenithy/run.js +44 -1
- package/package.json +46 -46
- package/pynithy/build/securelock/Dockerfile.tmpl +18 -2
- package/pynithy/build.mjs +31 -5
- package/pynithy/ipfs.mjs +376 -155
- package/pynithy/run/docker-compose-final.yml.tmpl +9 -0
- package/pynithy/run/docker-compose.yml.tmpl +10 -6
- package/pynithy/run.js +40 -1
|
@@ -28,6 +28,9 @@ services:
|
|
|
28
28
|
- SCONE_LAS_ADDR=las
|
|
29
29
|
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
|
|
30
30
|
- SCONE_HEAP=1024M
|
|
31
|
+
# Must match the --stack the securelock binary was signed with (build.mjs),
|
|
32
|
+
# otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
|
|
33
|
+
- SCONE_STACK=4M
|
|
31
34
|
- SCONE_LOG=DEBUG
|
|
32
35
|
- SCONE_ALLOW_DLOPEN=1
|
|
33
36
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# Publish-time compose: used by ecld-publish to compute the MRENCLAVE
|
|
2
|
+
# (SCONE_HASH=1) and extract the enclave certificate LOCALLY. It intentionally
|
|
3
|
+
# does NOT mount /dev/sgx_enclave so it runs on a developer machine WITHOUT SGX
|
|
4
|
+
# (SCONE_HASH is a build measurement; enclaves reach SGX via LAS over TCP). When
|
|
5
|
+
# no local SGX is present, cert extraction falls back to the remote
|
|
6
|
+
# publickey.ethernity.cloud service. The on-node runtime uses
|
|
7
|
+
# docker-compose-final.yml, which targets real SGX hardware.
|
|
1
8
|
version: '3.2'
|
|
2
9
|
services:
|
|
3
10
|
las:
|
|
@@ -6,8 +13,6 @@ services:
|
|
|
6
13
|
image: localhost:5000/etny-las
|
|
7
14
|
entrypoint: "/las_entrypoint.sh"
|
|
8
15
|
command: "/usr/local/bin/las"
|
|
9
|
-
devices:
|
|
10
|
-
- "/dev/sgx_enclave:/dev/sgx_enclave"
|
|
11
16
|
restart: on-failure
|
|
12
17
|
ports:
|
|
13
18
|
- target: 18766
|
|
@@ -26,12 +31,13 @@ services:
|
|
|
26
31
|
- SCONE_LAS_ADDR=las
|
|
27
32
|
- SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
|
|
28
33
|
- SCONE_HEAP=1024M
|
|
34
|
+
# Must match the --stack the securelock binary was signed with (build.mjs),
|
|
35
|
+
# otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
|
|
36
|
+
- SCONE_STACK=4M
|
|
29
37
|
- SCONE_LOG=DEBUG
|
|
30
38
|
- SCONE_ALLOW_DLOPEN=1
|
|
31
39
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
32
40
|
restart: on-failure:5
|
|
33
|
-
devices:
|
|
34
|
-
- "/dev/sgx_enclave:/dev/sgx_enclave"
|
|
35
41
|
depends_on:
|
|
36
42
|
- las
|
|
37
43
|
|
|
@@ -50,8 +56,6 @@ services:
|
|
|
50
56
|
- SCONE_ALLOW_DLOPEN=1
|
|
51
57
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
52
58
|
restart: on-failure
|
|
53
|
-
devices:
|
|
54
|
-
- "/dev/sgx_enclave:/dev/sgx_enclave"
|
|
55
59
|
depends_on:
|
|
56
60
|
- las
|
|
57
61
|
|
|
@@ -70,7 +74,5 @@ services:
|
|
|
70
74
|
- SCONE_ALLOW_DLOPEN=1
|
|
71
75
|
- SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
72
76
|
restart: on-failure:5
|
|
73
|
-
devices:
|
|
74
|
-
- "/dev/sgx_enclave:/dev/sgx_enclave"
|
|
75
77
|
depends_on:
|
|
76
78
|
- las
|
package/nodenithy/run.js
CHANGED
|
@@ -73,6 +73,30 @@ const runDockerCommand = (service) => {
|
|
|
73
73
|
return output.split('\n').filter(line => !/Creating|Pulling|latest|Digest/.test(line)).join('');
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
// Extract the runtime enclave measurement (SCONE_HASH) as a bare 64-hex string.
|
|
77
|
+
// Mirrors Python extract_scone_hash: the enclave prints its MRENCLAVE somewhere
|
|
78
|
+
// in the SCONE_HASH=1 output; pick the first 64-hex token.
|
|
79
|
+
const extractSconeHash = (service) => {
|
|
80
|
+
const output = runDockerCommand(service);
|
|
81
|
+
const m = output.match(/\b[a-fA-F0-9]{64}\b/);
|
|
82
|
+
if (!m) throw new Error(`No SHA256 MRENCLAVE found in ${service} SCONE_HASH output.`);
|
|
83
|
+
return m[0];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Read the MRENCLAVE that was signed at build time (baked into the image as
|
|
87
|
+
// /signed_mrenclave.txt by the securelock Dockerfile on mainnet). Mirrors Python
|
|
88
|
+
// extract_signed_mrenclave. Returns '' if the file is absent (testnet builds).
|
|
89
|
+
const extractSignedMrenclave = (service) => {
|
|
90
|
+
try {
|
|
91
|
+
const command = `docker-compose -f docker-compose.yml run --no-deps --entrypoint cat ${service} /signed_mrenclave.txt`;
|
|
92
|
+
const output = execSync(command, { stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
|
|
93
|
+
const m = output.match(/\b[a-fA-F0-9]{64}\b/);
|
|
94
|
+
return m ? m[0] : '';
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
76
100
|
const main = async () => {
|
|
77
101
|
process.env.NODE_NO_WARNINGS = 1
|
|
78
102
|
const backupFiles = ['docker-compose.yml.tmpl', 'docker-compose-final.yml.tmpl'];
|
|
@@ -86,13 +110,32 @@ const main = async () => {
|
|
|
86
110
|
});
|
|
87
111
|
|
|
88
112
|
|
|
89
|
-
process.env.MRENCLAVE_SECURELOCK =
|
|
113
|
+
process.env.MRENCLAVE_SECURELOCK = extractSconeHash('etny-securelock');
|
|
90
114
|
console.log(`MRENCLAVE_SECURELOCK: ${process.env.MRENCLAVE_SECURELOCK}`);
|
|
91
115
|
// process.env.MRENCLAVE_TRUSTEDZONE = runDockerCommand('etny-trustedzone');
|
|
92
116
|
// console.log(`MRENCLAVE_TRUSTEDZONE: ${process.env.MRENCLAVE_TRUSTEDZONE}`);
|
|
93
117
|
// process.env.MRENCLAVE_VALIDATOR = runDockerCommand('etny-validator');
|
|
94
118
|
// console.log(`MRENCLAVE_VALIDATOR: ${process.env.MRENCLAVE_VALIDATOR}`);
|
|
95
119
|
|
|
120
|
+
// ----- MRENCLAVE match-gate (mainnet) -----
|
|
121
|
+
// The runtime enclave measured just above (SCONE_HASH, with the same env the
|
|
122
|
+
// harvest uses) MUST match the MRENCLAVE that was signed --production at build
|
|
123
|
+
// time (baked into /signed_mrenclave.txt). A mismatch means SCONE recomputed
|
|
124
|
+
// the measurement at load because the runtime enclave-creation params drifted
|
|
125
|
+
// from the signed binary -- and a runtime recompute yields a DEBUG enclave
|
|
126
|
+
// that CAS rejects ("Debug mode is enabled"). Refuse to publish rather than
|
|
127
|
+
// register an untrusted identity on-chain or fail opaquely later.
|
|
128
|
+
if (isMainnet) {
|
|
129
|
+
const signedMrenclave = extractSignedMrenclave('etny-securelock');
|
|
130
|
+
if (!signedMrenclave || signedMrenclave !== process.env.MRENCLAVE_SECURELOCK) {
|
|
131
|
+
console.error(`Error: securelock runtime MRENCLAVE (${process.env.MRENCLAVE_SECURELOCK}) != signed MRENCLAVE (${signedMrenclave}).`);
|
|
132
|
+
console.error(' The runtime enclave differs from the --production-signed binary (SCONE recomputed -> debug).');
|
|
133
|
+
console.error(' Refusing to publish. Rebuild with the current SDK (ecld-build) and retry.');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
console.log('\t✔ MRENCLAVE match-gate passed: runtime enclave matches the --production-signed MRENCLAVE');
|
|
137
|
+
}
|
|
138
|
+
|
|
96
139
|
writeEnv('MRENCLAVE_SECURELOCK', process.env.MRENCLAVE_SECURELOCK);
|
|
97
140
|
// writeEnv('MRENCLAVE_TRUSTEDZONE', process.env.MRENCLAVE_TRUSTEDZONE);
|
|
98
141
|
// writeEnv('MRENCLAVE_VALIDATOR', process.env.MRENCLAVE_VALIDATOR);
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
2
|
+
"name": "@ethernity-cloud/sdk-js",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A package to run ethernity cloud build scripts",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"ecld-build": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/build.js",
|
|
7
|
+
"ecld-publish": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/publish.js",
|
|
8
|
+
"ecld-init": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/init.js",
|
|
9
|
+
"ecld-run": "cross-env ecld-run",
|
|
10
|
+
"postinstall": "node postinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"ecld-build": "./build.js",
|
|
14
|
+
"ecld-publish": "./publish.js",
|
|
15
|
+
"ecld-init": "./init.js",
|
|
16
|
+
"postinstall": "./postinstall.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"nodenithy/*",
|
|
20
|
+
"pynithy/*",
|
|
21
|
+
"build.js",
|
|
22
|
+
"init.js",
|
|
23
|
+
"publish.js",
|
|
24
|
+
"postinstall.js"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@ethernity-cloud/runner": "^0.0.26",
|
|
28
|
+
"axios": "^1.7.2",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"dotenv": "^16.4.5",
|
|
31
|
+
"eth-lib": "^0.1.29",
|
|
32
|
+
"ethers": "^5.5.4",
|
|
33
|
+
"kubo-rpc-client": "^4.1.1",
|
|
34
|
+
"web3": "^4.9.0",
|
|
35
|
+
"cross-env": "^7.0.3",
|
|
36
|
+
"shelljs": "^0.8.5",
|
|
37
|
+
"shx": "^0.3.4",
|
|
38
|
+
"node-forge": "^1.3.1",
|
|
39
|
+
"cli-progress": "^3.12.0",
|
|
40
|
+
"react": "^18.3.1",
|
|
41
|
+
"react-dom": "^18.3.1",
|
|
42
|
+
"react-scripts": "^5.0.1",
|
|
43
|
+
"web-vitals": "^2.1.4",
|
|
44
|
+
"adm-zip": "^0.5.14"
|
|
45
|
+
},
|
|
46
|
+
"author": "Alexandru Luca",
|
|
47
|
+
"license": "AGPL-3.0"
|
|
48
48
|
}
|
|
@@ -64,13 +64,29 @@ COPY --from=build /libbinary-fs.so /lib/libbinary-fs.so
|
|
|
64
64
|
|
|
65
65
|
RUN openssl genrsa -3 -out /enclave-key.pem 3072
|
|
66
66
|
|
|
67
|
+
# SCONE runtime env. Must match the enclave-creation params passed to
|
|
68
|
+
# scone-signer below (any drift makes SCONE recompute + debug re-sign at load,
|
|
69
|
+
# which CAS rejects on mainnet). Values mirror the run/docker-compose securelock
|
|
70
|
+
# service (SCONE_HEAP=1024M, SCONE_STACK=4M, dlopen=1, extensions=libbinary-fs).
|
|
67
71
|
ENV SCONE_LOG=INFO
|
|
68
72
|
ENV SCONE_HEAP=1024M
|
|
73
|
+
ENV SCONE_STACK=4M
|
|
69
74
|
ENV SCONE_ALLOW_DLOPEN=1
|
|
70
75
|
ENV SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
71
76
|
|
|
72
|
-
#
|
|
73
|
-
#
|
|
77
|
+
# Rendered by build.mjs. Signs the EXECUTED binary (/usr/local/bin/python -- the
|
|
78
|
+
# ENTRYPOINT and the run/publish compose command both run /usr/local/bin/python;
|
|
79
|
+
# python3 is a SEPARATE symlink, signing it leaves the executed binary
|
|
80
|
+
# debug-signed). Mainnet adds --production (CAS/DCAP); testnet omits it (non-CAS
|
|
81
|
+
# self-sign). On testnet builds this line is a no-op comment.
|
|
82
|
+
__SCONE_SIGN__
|
|
83
|
+
|
|
84
|
+
# Record the SIGNED MRENCLAVE so publish (run.js) can assert the runtime enclave
|
|
85
|
+
# measurement matches what was signed before registering the CAS session. Only
|
|
86
|
+
# produced for --production (mainnet); on testnet the file is absent and the
|
|
87
|
+
# check is skipped. Capture to a file first (piping scone-signer into head/grep
|
|
88
|
+
# kills it with SIGPIPE / exit 141 under buildkit's pipefail shell).
|
|
89
|
+
__SIGNED_MRENCLAVE__
|
|
74
90
|
|
|
75
91
|
RUN rm -rf /enclave-key.pem
|
|
76
92
|
|
package/pynithy/build.mjs
CHANGED
|
@@ -122,17 +122,43 @@ console.log('Building etny-securelock');
|
|
|
122
122
|
process.chdir('securelock');
|
|
123
123
|
// runCommand(`cat Dockerfile.tmpl | sed s/"__ENCLAVE_NAME_SECURELOCK__"/"${ENCLAVE_NAME_SECURELOCK}"/g > Dockerfile`);
|
|
124
124
|
const dockerfileSecureTemplate = fs.readFileSync('Dockerfile.tmpl', 'utf8');
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
let dockerfileSecureContent = dockerfileSecureTemplate.replace(/__ENCLAVE_NAME_SECURELOCK__/g, ENCLAVE_NAME_SECURELOCK).replace(/__BUCKET_NAME__/g, templateName + "-v3").replace(/__SMART_CONTRACT_ADDRESS__/g, ECRunner[templateName][0]).replace(/__IMAGE_REGISTRY_ADDRESS__/g, ECRunner[templateName][1]).replace(/__RPC_URL__/g, ECRunner[templateName][2]).replace(/__CHAIN_ID__/g, ECRunner[templateName][3]).replace(/__TRUSTED_ZONE_IMAGE__/g, templateName);
|
|
126
|
+
|
|
127
|
+
// Amount of enclave heap to allocate (SCONE_HEAP); overridable, in sync with the
|
|
128
|
+
// run/docker-compose securelock service.
|
|
129
|
+
const MEMORY_TO_ALLOCATE = (process.env.ECLD_MEMORY_TO_ALLOCATE || '1024M').trim();
|
|
130
|
+
|
|
131
|
+
// CRITICAL (mainnet DCAP): sign /usr/local/bin/python -- the binary the enclave
|
|
132
|
+
// actually EXECUTES (ENTRYPOINT + run/publish compose command both run
|
|
133
|
+
// /usr/local/bin/python). /usr/local/bin/python3 is a SEPARATE symlink; signing
|
|
134
|
+
// it leaves the executed binary DEBUG-signed, so SCONE recomputes MRENCLAVE at
|
|
135
|
+
// load and re-signs as debug -> CAS rejects the DCAP quote on mainnet.
|
|
136
|
+
// The enclave-creation params MUST match the runtime env exactly.
|
|
137
|
+
const signFlags =
|
|
138
|
+
`--key=/enclave-key.pem --env --heap=${MEMORY_TO_ALLOCATE} ` +
|
|
139
|
+
`--stack=4M --dlopen=1 --extensions=/lib/libbinary-fs.so`;
|
|
140
|
+
|
|
141
|
+
const signedMrenclaveStep =
|
|
142
|
+
'RUN scone-signer info /usr/local/bin/python > /tmp/siginfo.txt 2>&1; \\\n' +
|
|
143
|
+
' grep -iE "MRENCLAVE:" /tmp/siginfo.txt | sed -n \'1p\' \\\n' +
|
|
144
|
+
' | sed -E \'s/.*MRENCLAVE:[[:space:]]*//I\' | tr -d \'[:space:]\' > /signed_mrenclave.txt && \\\n' +
|
|
145
|
+
' echo "SIGNED_MRENCLAVE=$(cat /signed_mrenclave.txt)"';
|
|
128
146
|
|
|
129
147
|
let imagesTag = process.env.BLOCKCHAIN_NETWORK.toLowerCase();
|
|
130
148
|
|
|
131
149
|
if (isMainnet) {
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
dockerfileSecureContent = dockerfileSecureContent
|
|
151
|
+
.replace('__SCONE_SIGN__', `RUN scone-signer sign ${signFlags} --production /usr/local/bin/python`)
|
|
152
|
+
.replace('__SIGNED_MRENCLAVE__', signedMrenclaveStep);
|
|
153
|
+
imagesTag = process.env.BLOCKCHAIN_NETWORK.split("_")[0].toLowerCase();
|
|
154
|
+
} else {
|
|
155
|
+
dockerfileSecureContent = dockerfileSecureContent
|
|
156
|
+
.replace('__SCONE_SIGN__', '# testnet: non-CAS self-sign (no --production sign at build time)')
|
|
157
|
+
.replace('__SIGNED_MRENCLAVE__', '# testnet: no signed MRENCLAVE (self-signed from MR_ENCLAVE at runtime)');
|
|
134
158
|
}
|
|
135
159
|
|
|
160
|
+
fs.writeFileSync('Dockerfile', dockerfileSecureContent);
|
|
161
|
+
|
|
136
162
|
|
|
137
163
|
|
|
138
164
|
|