Trusted Execution Environments offer a huge advantage from a security perspective. They guarantee that the behavior of execution does not change even when launched on an untrusted remote machine. The data inside this type of environment is also protected, which allows its monetization while preventing leakage.
With iExec, it is possible to securely associate an application developer secret to the runtime of an application.
The app developer secret is only exposed to your app inside authorized enclaves and never leaves them.
Your secrets are securely transferred with the SDK from your machine to the SMS over a TLS channel. Internally, your secrets are encrypted with standard AES encryption before being written to disk. Next releases will feature an SMS running entirely inside a trusted enclave.
Let's see how to do all of that!
Prepare your application
We will use the API countapi.xyz. This service keeps a count of hits on any couple of namespace/key (ex: https://api.countapi.xyz/hit/foo/bar). In this example, we will use an app developer secret to set namespace/key.
Let's create a directory tree for this app in ~/iexec-projects/.
The application uses the developer secret to make a call to a secret endpoint of countapi.xyz and writes the result in a file:
Copy the following content in src/ .
src/app.js
constfsPromises=require("fs").promises;constaxios=require("axios");(async () => {try {constiexecOut=process.env.IEXEC_OUT;// get the secret endpoint from app developer secretconstsecret=process.env.IEXEC_APP_DEVELOPER_SECRET;if (!secret) {console.log("missing IEXEC_APP_DEVELOPER_SECRET");process.exit(1); }// get the hit count from countapiconsthitCount=await axios.get(`https://api.countapi.xyz/hit/iexec/${secret}`).then(({ data }) =>data.value);constresult=`endpoint hit ${hitCount} times`;console.log(result);// write the resultawaitfsPromises.writeFile(`${iexecOut}/result.txt`, result);// declare everything is computedconstcomputedJsonObj= {"deterministic-output-path":`${iexecOut}/result.txt`, };awaitfsPromises.writeFile(`${iexecOut}/computed.json`,JSON.stringify(computedJsonObj) ); } catch (e) {// do not log anything that could reveal the app developer secret!console.log("something went wrong");process.exit(1); }})();
src/app.py
import osimport jsonimport requeststry: iexec_out = os.environ["IEXEC_OUT"]# get the secret endpoint from app developer secrettry: secret = os.environ["IEXEC_APP_DEVELOPER_SECRET"]exceptException:print("missing IEXEC_APP_DEVELOPER_SECRET")exit(1)# get the hit count from countapi response = requests.request("GET", "https://api.countapi.xyz/hit/iexec/"+ secret) json_response = response.json() hit_count = json_response["value"] result ="endpoint hit "+str(hit_count)+" times"print(result)# write the resultwithopen(iexec_out +"/result.txt", "w+")as fout: fout.write(result)# declare everything is computedwithopen(iexec_out +"/computed.json", "w+")as f: json.dump({ "deterministic-output-path" : iexec_out +"/result.txt" }, f)exceptException:# do not log anything that could reveal the app developer secret!print("something went wrong")exit(1)
Build the TEE docker image
The Dockerfile and the build scripts are similar to the ones we saw previously for a trusted application:
Dockerfile
# Starting from a base image supported by SCONEFROMnode:14-alpine3.11# install your dependenciesRUNmkdir/app&&cd/app&&npminstallaxiosCOPY./src/appENTRYPOINT [ "node","/app/app.js"]
#!/bin/bash# declare the app entrypointENTRYPOINT="node /app/app.js"# declare an image nameIMG_NAME=tee-developer-secret-appIMG_FROM=${IMG_NAME}:temp-non-teeIMG_TO=${IMG_NAME}:tee-debug# build the regular non-TEE imagedockerbuild.-t ${IMG_FROM}# pull the SCONE curated image corresponding to our base imagedockerpullregistry.scontain.com/sconecuratedimages/node:14.4.0-alpine3.11# run the sconifier to build the TEE image based on the non-TEE imagedockerrun-it--rm \-v/var/run/docker.sock:/var/run/docker.sock \registry.scontain.com/scone-production/iexec-sconify-image:5.3.15-v12 \sconify_iexec \--name=${IMG_NAME} \--from=${IMG_FROM} \--to=${IMG_TO} \--binary-fs \--fs-dir=/app \--host-path=/etc/hosts \--host-path=/etc/resolv.conf \--binary=/usr/local/bin/node \--heap=1G \--dlopen=2 \--no-color \--verbose \--command=${ENTRYPOINT} \&&echo-e"\n------------------\n" \&&echo"successfully built TEE docker image => ${IMG_TO}" \&&echo"application mrenclave.fingerprint is $(dockerrun-it--rm-eSCONE_HASH=1 ${IMG_TO})"
sconify.sh
#!/bin/bash# declare the app entrypointENTRYPOINT="python3 /app/app.py"# declare an image nameIMG_NAME=tee-developer-secret-appIMG_FROM=${IMG_NAME}:temp-non-teeIMG_TO=${IMG_NAME}:tee-debug# build the regular non-TEE imagedockerbuild.-t ${IMG_FROM}# run the sconifier to build the TEE image based on the non-TEE imagedockerrun-it \-v/var/run/docker.sock:/var/run/docker.sock \registry.scontain.com/scone-production/iexec-sconify-image:5.3.15-v12 \sconify_iexec \--name=${IMG_NAME} \--from=${IMG_FROM} \--to=${IMG_TO} \--binary-fs \--fs-dir=/app \--host-path=/etc/hosts \--host-path=/etc/resolv.conf \--binary=/usr/local/bin/python3.7 \--heap=1G \--dlopen=2 \--no-color \--verbose \--command=${ENTRYPOINT} \&&echo-e"\n------------------\n" \&&echo"successfully built TEE docker image => ${IMG_TO}" \&&echo"application mrenclave.fingerprint is $(dockerrun-it--rm-eSCONE_HASH=1 ${IMG_TO})"
Run the sconify.sh script to build the TEE-debug app.
The sconify.sh script prints the generated docker image name, you must retag this image and push it on dockerhub.
Test your app on iExec
At this stage, your application is ready to be tested on iExec. The process is similar to testing any type of application on the platform, with these minor exceptions:
Deploy the TEE app on iExec
TEE applications require some additional information to be filled in during deployment.
# prepare the TEE application templateiexecappinit--tee
Edit iexec.json and fill in the standard keys and the mrenclave object:
{ ..."app": {"owner":"0xF048eF3d7E3B33A465E0599E641BB29421f7Df92",// your address"name":"tee-developer-secret-app",// application name"type":"DOCKER","multiaddr":"docker.io/username/tee-developer-secret-app:1.0.0",// app image"checksum":"0xf997788fcb5c9a47d8fa2653098da3c58343d400a82ca13d014d711d60560cac",// image digest"mrenclave": {"provider":"SCONE",// TEE provider (keep default value)"version":"v5",// Scone version (keep default value)"entrypoint":"node /app/app.js" OR "python3 /app/app.py",// your app image entrypoint"heapSize":1073741824,// heap size in bytes (1GB) "fingerprint": "7d264f09de532fb1d55d25c4eb345a26454f4c21a1379e3813570538124a158e" // fingerprint of the enclave code (mrenclave), see how to retrieve it below
} }, ...}
Run your TEE image with SCONE_HASH=1 to get the enclave fingerprint (mrenclave):
You will get a hexadecimal address for your deployed app. Use that address to push the app developer secret to the SMS.
For simplicity, we will use the secret in a TEE-debug app on a debug workerpool. The debug workerpool is connected to a debug Secret Management Service so we will send the dataset encryption key to this SMS (this is fine for debugging but do not use to store production secrets).
These sed commands will do the trick:
# set a custom bellecour SMS in chain.jsonsed-i's|"bellecour": {},|"bellecour": { "sms": "https://v7.sms.debug-tee-services.bellecour.iex.ec" },|g'chain.json
# push the app developer secret to the SMSiexecapppush-secret--chainbellecour# check the secret is available on the SMSiexecappcheck-secret--chainbellecour
# restore the default configuration in chain.jsonsed-i's|"bellecour": { "sms": "https://v7.sms.debug-tee-services.bellecour.iex.ec" },|"bellecour": {},|g'chain.json
Run the TEE app
Specify the tag --tag tee in iexec app run command to run a tee app with an app developer secret.
One last thing, in order to run a TEE-debug app you will also need to select a debug workerpool, use the debug workerpool v7-debug.main.pools.iexec.eth.