Skip to content

Node Example with Attestation and Secret Provisioning

We show how to inject keys (/tls/key.pem) and certificate ('/tls/cert.pem) in a simple node program with a SCONE policy:

var express = require('express');
const https = require("https"),
fs = require("fs");

console.log('Example tls app start!');
console.log('read the secret :'+process.env.GREETING)

const keyFilePath = '/tls/key.pem';
const certFilePath = '/tls/cert.pem';


var privateKey  = fs.readFileSync(keyFilePath, 'utf8');
var certificate = fs.readFileSync(certFilePath, 'utf8');

var credentials = {key: privateKey, cert: certificate};

var app = express();
var httpsServer = https.createServer(credentials, app);


app.get('/', function (req, res) {
  console.log('scone mode is :'+process.env.GREETING)
  res.send('Hello World!' + process.env.GREETING);
});

httpsServer.listen(443, function () {
  console.log('Example tls app listening on port 443!');
  console.log('scone mode is :'+process.env.GREETING)
  console.log('Ok.');
});

A policy template defines a private key and a certificate that is signed with a CA certificate that is also created in this policy. We set the DNS name of the certifcate to app. We define a secret GREETING: "Hello NodeJS" as an environment variable passed to app. In this policy, we assume that our development machine has not seen many firmware updates recently:

name: $NODE_SESSION
version: "0.3"

# Access control:
#   - only the data owner (CREATOR) can read or update the session
#   - even the data owner cannot read the session secrets (i.e., the volume key and tag) or delete the session

access_policy:
  read:
   - CREATOR
  update:
   - CREATOR

services:
   - name: app
     image_name: node_image
     mrenclaves: [$MRENCLAVE]
     command: node /app/app.js
     environment:
      SCONE_MODE: hw
      SCONE_LOG: "7"
      GREETING: "Hello NodeJS"
     pwd: /

images:
   - name: node_image
     injection_files:
         - path:  /tls/cert.pem
           content: $$SCONE::app.crt$$
         - path: /tls/key.pem
           content: $$SCONE::app.key$$

# Import client credentials from DB session.
secrets:
    - name: api_ca_key
      kind: private-key
    - name: api_ca_cert
      kind: x509-ca
      export_public: true
      private_key: api_ca_key
    - name: app_key
      kind: private-key
    - name: app
      kind: x509
      private_key: app_key
      issuer: api_ca_cert
      dns:
        - app


security:
  attestation:
    tolerate: [debug-mode, hyperthreading, insecure-igpu, outdated-tcb]
    ignore_advisories: ["INTEL-SA-00076", "INTEL-SA-00088", "INTEL-SA-00106", "INTEL-SA-00115", "INTEL-SA-00135", "INTEL-SA-00203", "INTEL-SA-00161", "INTEL-SA-00220", "INTEL-SA-00270", "INTEL-SA-00293", "INTEL-SA-00320", "INTEL-SA-00329", "INTEL-SA-00233", "INTEL-SA-00220", "INTEL-SA-00270", "INTEL-SA-00293", "INTEL-SA-00320", "INTEL-SA-00329"]

We need to set the MRENCLAVE and the name of the policy (NODE_SESSION) before we upload the session to a CAS instance. To simplify this upload and running of the node application, we pushed the code to github. Just clone it and enter the newly cloned directory

git clone https://github.com/scontain/node_example.git
cd node_example

We need to set some environment variables and upload our session:

export CAS_ADDR="4-2-1.scone-cas.cf" # we use a public SCONE CAS to store the session policies
export IMAGE="sconecuratedimages/apps:node-10.14-alpine"
unset NODE_SESSION
export NODE_SESSION=$(./upload_session --template=nodejs-template.yml --session=nodejs-session.yml  --image=$IMAGE --cas=$CAS_ADDR)
export DEVICE=$(./determine_sgx_device) # determine the SGX device of the local computer

and then run locally by executing

docker-compose up

This will print some log messages and eventually print

node_1  | Example tls app start!
node_1  | read the secret :Hello NodeJS
node_1  | Example tls app listening on port 443!
node_1  | scone mode is :Hello NodeJS
node_1  | Ok.

indicating that the app is ready to process requests.

Client Request

Execute client request via https:

curl -k https://localhost:443

The output will be:

Hello World!Hello NodeJS

Note that '-k' is the insecure mode and if we drop this option, the output will look like this:

curl https://localhost:443

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Let's fix this warning and by doing so we also attest the node service.

Get CA Certificate used to sign certificate of node app

Retrieve the exported ca certificate with curl from CAS. Note that we told CAS to export the CA certifcate by setting export_public: truein the policy. We store the certificate in file `ca-cert':

export ca_cert=$(curl -k https://${CAS_ADDR}:8081/v1/values/session=$NODE_SESSION | jq ".values.api_ca_cert.value" | tr -d \" )
printf "\n$ca_cert" > ca_cert

We can now use this ca certificate to verify the certificate from our application:

curl --capath "$(pwd)" --cacert ca_cert --verbose https://app:443 --resolve app:443:127.0.0.1

Notes

  • Before restarting the service, please shut it down properly with docker-compose down. Also execute unset NODE_SESSION to ensure that you do not reuse an old session afterwards.

  • You need to ensure that CAS executes inside of an enclave in production mode. To do so, you would need to use our SCONE CLI to attest CAS and to upload a session

  • You need to encrypt the app code and parts of the node image before you go to production. See our flask exmaple on how to do this.