Skip to content

Commit e4b6277

Browse files
feat: migrate to Scone prod (#224)
* feat: Scone prod * ci: use enclave-key from server * fix: remove useless sconification option "command" * fix: check enclave key read access before mounting * docs: add enclave signing key doc * fix: log warn deprecated sconeProd === false
1 parent 4f267a8 commit e4b6277

15 files changed

Lines changed: 118 additions & 49 deletions

File tree

.github/workflows/reusable-api-deploy.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ jobs:
5858
fi
5959
shell: bash
6060

61+
- name: Check configuration files on server
62+
run: |
63+
ssh -o StrictHostKeyChecking=no \
64+
-i ~/.ssh/ghrunnerci \
65+
${{ secrets.host }} << 'EOF'
66+
cd /opt/iapp-api
67+
missing=0
68+
if [ ! -f .env.app ]; then
69+
echo ".env.app file not found on remote server"
70+
missing=1
71+
fi
72+
if [ ! -f sig/enclave-key.pem ]; then
73+
echo "sig/enclave-key.pem not found on remote server"
74+
missing=1
75+
fi
76+
if [ "$missing" -ne 0 ]; then
77+
exit 1
78+
fi
79+
EOF
80+
shell: bash
81+
6182
- name: Prepare .env for Compose
6283
run: |
6384
printf "IMAGE_NAME=%s\nIMAGE_TAG=%s\n" "${{ env.IMAGE_NAME }}" "${{ inputs.tag }}"> .env

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
node_modules
44
api/.env
5+
api/sig
56
.tags
67

78
cli/dist

api/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ The API is composed of:
2929
- Node 20
3030
- docker installed locally with support for linux/amd64 architecture (either
3131
native or emulated)
32-
- Scontain account whit pull access to docker repository
32+
- Scontain account with pull access to docker repository
3333
`registry.scontain.com/scone-production/iexec-sconify-image`
34+
- An enclave signing key to sign Scone production images
3435

3536
Create a `.env` file see [`.env.template`](.env.template)
3637

@@ -39,6 +40,18 @@ cp .env.template .env
3940
# fill in the .env file
4041
```
4142

43+
Create or provide your own enclave signing key in `sig/enclave-key.pem` to sign
44+
Scone production images
45+
46+
> The enclave signing key should be a PEM formatted RSA key 3072 bits
47+
>
48+
> A valid signing key can be generated with openssl by running
49+
> `openssl genrsa -3 3072`
50+
51+
```sh
52+
npm run ensure-signing-key
53+
```
54+
4255
## run locally
4356

4457
```sh

api/docker-compose.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ services:
88
volumes:
99
- /var/run/docker.sock:/var/run/docker.sock
1010
# .env.app already on the server
11-
- ./.env.app:/app/.env
11+
- ./.env.app:/app/.env:ro
12+
# enclave key already on the server in sig/enclave-key.pem
13+
- ./sig/:/app/sig/:ro
1214
healthcheck:
1315
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
1416
interval: 30s

api/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
"main": "index.js",
55
"type": "module",
66
"scripts": {
7-
"start": "tsx --env-file=.env ./src/index.js",
8-
"dev": "tsx --env-file=.env --watch ./src/index.js",
9-
"dev:pretty": "tsx --env-file=.env --watch ./src/index.js | pino-pretty -tc",
7+
"ensure-signing-key": "[ -e 'sig/enclave-key.pem' ] && echo 'using existing signing key' || (mkdir -p sig && openssl genrsa -3 -out sig/enclave-key.pem 3072 && echo 'generated new signing key')",
8+
"start": "npm run ensure-signing-key && tsx --env-file=.env ./src/index.js",
9+
"dev": "npm run ensure-signing-key && tsx --env-file=.env --watch ./src/index.js",
10+
"dev:pretty": "npm run ensure-signing-key && tsx --env-file=.env --watch ./src/index.js | pino-pretty -tc",
1011
"check-format": "prettier --check .",
1112
"check-types": "tsc --project tsconfig.json",
1213
"format": "prettier --write .",

api/src/sconify/deprecated_sconify.service.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ export async function deprecated_sconify({
131131
sconifiedImageId = await sconifyImage({
132132
fromImage: dockerImageToSconify,
133133
sconifyVersion,
134-
entrypoint: appEntrypoint,
135134
binary: configTemplate.binary,
136135
});
137136
logger.info({ sconifiedImageId }, 'Sconified successfully');

api/src/sconify/sconifyBuild.handler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const bodySchema = z.object({
2929
.enum(Object.keys(TEMPLATE_CONFIG) as [TemplateName])
3030
.default('JavaScript'),
3131
sconeVersion: z.enum(['v5', 'v5.9']).default('v5'),
32+
sconeProd: z.boolean().default(false),
3233
});
3334

3435
async function handleSconifyRequest(requestObj: object) {
@@ -37,13 +38,15 @@ async function handleSconifyRequest(requestObj: object) {
3738
let dockerhubPushToken: string;
3839
let sconeVersion: SconeVersion;
3940
let template: TemplateName;
41+
let sconeProd: boolean;
4042
try {
4143
({
4244
yourWalletPublicAddress,
4345
dockerhubImageToSconify,
4446
dockerhubPushToken,
4547
sconeVersion,
4648
template,
49+
sconeProd,
4750
} = bodySchema.parse(requestObj));
4851
} catch (error) {
4952
throw fromError(error, {
@@ -58,6 +61,9 @@ async function handleSconifyRequest(requestObj: object) {
5861
if (sconeVersion === 'v5') {
5962
logger.warn('Deprecated feature hit: sconeVersion === "v5"');
6063
}
64+
if (sconeProd === false) {
65+
logger.warn('Deprecated feature hit: sconeProd === false');
66+
}
6167

6268
const { dockerImage, dockerImageDigest, fingerprint, entrypoint } =
6369
await sconify({
@@ -66,6 +72,7 @@ async function handleSconifyRequest(requestObj: object) {
6672
userWalletPublicAddress: yourWalletPublicAddress,
6773
sconeVersion,
6874
templateLanguage: template,
75+
sconeProd,
6976
});
7077
return {
7178
dockerImage,

api/src/sconify/sconifyBuild.service.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function sconify({
2323
pushToken,
2424
sconeVersion,
2525
templateLanguage,
26+
sconeProd = false,
2627
}: {
2728
/**
2829
* Examples of valid dockerImageToSconify:
@@ -39,6 +40,7 @@ export async function sconify({
3940
pushToken: string;
4041
templateLanguage: TemplateName;
4142
sconeVersion: SconeVersion;
43+
sconeProd?: boolean;
4244
}): Promise<{
4345
dockerImage: string;
4446
dockerImageDigest: string;
@@ -58,6 +60,7 @@ export async function sconify({
5860
templateLanguage,
5961
userWalletPublicAddress,
6062
wsEnabled,
63+
sconeProd,
6164
},
6265
'New sconify request'
6366
);
@@ -140,8 +143,8 @@ export async function sconify({
140143
sconifiedImageId = await sconifyImage({
141144
fromImage: dockerImageToSconify,
142145
sconifyVersion,
143-
entrypoint: appEntrypoint,
144146
binary: configTemplate.binary,
147+
prod: sconeProd,
145148
});
146149
logger.info({ sconifiedImageId }, 'Sconified successfully');
147150
} finally {
@@ -168,7 +171,7 @@ export async function sconify({
168171

169172
const imageRepo = `${dockerUserName}/${imageName}`;
170173
const sconifiedImageShortId = sconifiedImageId.substring(7, 7 + 12); // extract 12 first chars after the leading "sha256:"
171-
const sconifiedImageTag = `${imageTag}-tee-scone-${sconifyVersion}-debug-${sconifiedImageShortId}`; // add digest in tag to avoid replacing previous build
174+
const sconifiedImageTag = `${imageTag}-tee-scone-${sconifyVersion}-${sconeProd ? 'prod' : 'debug'}-${sconifiedImageShortId}`; // add digest in tag to avoid replacing previous build
172175
const sconifiedImage = `${imageRepo}:${sconifiedImageTag}`;
173176

174177
let pushed;

api/src/singleFunction/sconifyImage.ts

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { join } from 'node:path';
2+
import { access, constants } from 'node:fs/promises';
13
import Docker from 'dockerode';
24
import { SCONIFY_IMAGE_NAME } from '../constants/constants.js';
35
import { logger } from '../utils/logger.js';
@@ -8,14 +10,16 @@ import { removeContainer } from './removeContainer.js';
810

911
const docker = new Docker();
1012

13+
const ENCLAVE_KEY_PATH = join(process.cwd(), 'sig/enclave-key.pem');
14+
1115
/**
1216
* Sconifies an iapp docker image
1317
*/
1418
export async function sconifyImage({
1519
fromImage,
1620
sconifyVersion,
17-
entrypoint,
1821
binary,
22+
prod = false,
1923
}: {
2024
/**
2125
* image to sconify
@@ -25,42 +29,66 @@ export async function sconifyImage({
2529
* sconifier version
2630
*/
2731
sconifyVersion: string;
28-
/**
29-
* command to run the app (whitelisted)
30-
*/
31-
entrypoint: string;
3232
/**
3333
* whitelisted binary
3434
*/
3535
binary: string;
36+
/**
37+
* sconify production flag
38+
*/
39+
prod?: boolean;
3640
}): Promise<string> {
37-
logger.info({ fromImage, entrypoint }, 'Running sconify command...');
41+
logger.info(
42+
{ fromImage },
43+
`Running sconify command in ${prod ? 'prod' : 'debug'} mode...`
44+
);
3845
const sconifierImage = `${SCONIFY_IMAGE_NAME}:${sconifyVersion}`;
3946

4047
logger.info({ sconifierImage }, 'Pulling sconifier image...');
4148
await pullSconeImage(sconifierImage);
4249

50+
if (prod) {
51+
// check signing key can be read on host
52+
try {
53+
await access(ENCLAVE_KEY_PATH, constants.R_OK);
54+
} catch (error) {
55+
logger.error(
56+
{ error, path: ENCLAVE_KEY_PATH },
57+
'Cannot read enclave key from host'
58+
);
59+
throw new Error('Cannot read enclave key from host');
60+
}
61+
}
62+
4363
const toImage = `${fromImage}-tmp-sconified-${Date.now()}`; // create an unique temporary identifier for the target image
4464
logger.info({ fromImage, toImage }, 'Sconifying...');
65+
66+
const sconifyBaseCmd = [
67+
'sconify_iexec',
68+
`--from=${fromImage}`,
69+
`--to=${toImage}`,
70+
'--binary-fs',
71+
'--fs-dir=/app',
72+
'--host-path=/etc/hosts',
73+
'--host-path=/etc/resolv.conf',
74+
`--binary=${binary}`,
75+
'--heap=1G',
76+
'--dlopen=1',
77+
'--no-color',
78+
'--verbose',
79+
];
80+
81+
const baseBinds = ['/var/run/docker.sock:/var/run/docker.sock'];
82+
4583
const sconifyContainer = await docker.createContainer({
4684
Image: sconifierImage,
47-
Cmd: [
48-
'sconify_iexec',
49-
`--from=${fromImage}`,
50-
`--to=${toImage}`,
51-
'--binary-fs',
52-
'--fs-dir=/app',
53-
'--host-path=/etc/hosts',
54-
'--host-path=/etc/resolv.conf',
55-
`--binary=${binary}`,
56-
'--heap=1G',
57-
'--dlopen=1',
58-
'--no-color',
59-
'--verbose',
60-
`--command=${entrypoint}`,
61-
],
85+
Cmd: prod
86+
? sconifyBaseCmd.concat('--scone-signer=/sig/enclave-key.pem')
87+
: sconifyBaseCmd,
6288
HostConfig: {
63-
Binds: ['/var/run/docker.sock:/var/run/docker.sock'],
89+
Binds: prod
90+
? baseBinds.concat(`${ENCLAVE_KEY_PATH}:/sig/enclave-key.pem:ro`) // mount signing key
91+
: baseBinds,
6492
},
6593
});
6694

cli/src/cmd/debug.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ethers } from 'ethers';
22
import { askForWallet } from '../cli-helpers/askForWallet.js';
3-
import { getIExecDebug } from '../utils/iexec.js';
3+
import { getIExec } from '../utils/iexec.js';
44
import { getSpinner } from '../cli-helpers/spinner.js';
55
import * as color from '../cli-helpers/color.js';
66
import { handleCliError } from '../cli-helpers/handleCliError.js';
@@ -26,7 +26,7 @@ export async function debug({
2626
const chainConfig = getChainConfig(chainName);
2727
spinner.info(`Using chain ${chainName}`);
2828
const signer = await askForWallet({ spinner });
29-
const iexec = getIExecDebug({
29+
const iexec = getIExec({
3030
...chainConfig,
3131
signer,
3232
});

0 commit comments

Comments
 (0)