Skip to content

Secure Arguments and Environment Variables Tutorial

This tutorial introduces how to pass command line arguments and environment variables in a secure fashion to an application with the help of SCONE CAS (Configuration and Attestation Service). In this first part, we introduce SCONE policies. In the next parts, we iteratively introduce more concepts of SCONE: we motivate these concepts by first introducing security issues, and the show how to apply the concepts to address these issues.

SCONE CAS Overview

The center piece of the SCONE confidential computing platform is SCONE CAS which is a policy engine designed for confidential applications. In this tutorial we introduce step-by-step how CAS attests applications, generates keys, manages secrets, and provides secrets to application as arguments, environment variables, or via files injected into the filesystem of a confidential application. This is under complete control of the security policies of an application. The administrators of SCONE CAS (aka CAS owner) cannot access the secrets.

CAS policies define which services are authorized to access which secrets. Services are authenticated with the help of measurements by trusted entities. We call this attestation*. To minimize the required prerequisites, this first part does not use attestation. We introduce this **attestation in a later part of this tutorial.

Prerequisites

To execute the steps of this tutorial,

  • you need to have access to the SCONE community edition.

  • you need access to a modern x86 machine or an x86 emulator that emulates a modern x86 CPU with support for instructions like CPUID and RDRND.

Step 1: Without Attestation

We create a simple C program to print the arguments as well as the environment variables. You can clone the following repo from github:

git clone https://github.com/scontain/args-env-tutorial.git
cd args-env-tutorial/

Now, you should see the C program print-arg-env.c in your local directory. Alternatively, you could create this program as follows:

cat > print-arg-env.c <<EOF
#include <stdio.h>
#include <unistd.h>

// extern char **__environ;
extern char **environ;


int main (int argc, char **argv) {
    printf("argv:");
    for (int i = 0; i < argc; i++) {
        printf(" %s", argv[i]);
    }
    printf("\n");

    char** envp = environ;
    printf("environ:\n");
    while (*envp != NULL) {
        printf("%s\n", *envp);
        envp++;
    }
    return 42;
}
EOF

Native Execution

We can compile this natively as follows:

gcc print-arg-env.c -g -O3 -o print-arg-env

And we can now run this with some some arguments and some environment variables:

ENV2=World ENV1=! ./print-arg-env Hello

This results in an output similar to this:

argv: ./print-arg-env Hello
environ:
ENV2=World
ENV1=!
...

Confidential Execution

In the native execution, the arguments and environment variables is not only controlled by the shell in which the program is executing but also by the underlying operating system. This means that an adversary that controls the operating system, can control the environment variables and the arguments of our application.

Confidential computing helps to protect against such a system software adversary, i.e., an adversary that controls the hypervisor or the operating system. Let's cross-compile this program to run inside of enclaves, i.e. inside of an encrypted memory region that only the application itself can access in clear text. In a later step, we will show that one does not actually need to cross-compile the application: We can just perform a binary translation of a native application into a confidential application.

Running an application inside of an enclave requires access to an operating system device. In case of Intel SGX, this is the SGX device. This device had various names over the last years. We determine which SGX device to use with function determine_sgx_device. This function sets an environment variable MOUNT_SGXDEVICE. In case there is no SGX device available, this variable will be set to an empty string. In this case, our code runs in simulation mode, i.e., the application does NOT run inside an encrypted memory region.

source determine_sgx_device.sh
docker run $MOUNT_SGXDEVICE  --network=host --rm -it -v `pwd`:/work registry.scontain.com/sconecuratedimages/crosscompilers bash

Within this container, we have access to a cross-compiler. This cross-compiler creates a binary that is automatically executing in an enclave, i.e., an encrypted memory region if available:

# in the container compile the program:
scone-gcc /work/print-arg-env.c -g -O3 -o /work/scone-print-arg-env

The initial content of the enclave ist determined by the application binary. We MRENCLAVE is the value of a secure hash function when applied to the initial state of the enclave. We can now determine MRENCLAVE of program scone-print-arg-env as follows.

export MRENCLAVE=`SCONE_HASH=1 /work/scone-print-arg-env`
echo MRENCLAVE of scone-print-arg-env is $MRENCLAVE

This results in some output similar to this:

MRENCLAVE of scone-print-arg-env is d385e09d99ae0f82e1cb93e6359e39813031610ee8488a8c24b76b00f1d9d6e3

We can now run the program inside of an enclave but not under the control of CAS as follows:

ENV2=World ENV1=! /work/scone-print-arg-env Hello

This will result in the same output as before:

[SCONE|WARN] src/enclave/dispatch.c:140:print_runtime_info(): Application runs in SGX simulation mode. Its memory can be read by the untrusted system! This is not secure!
argv: /work/scone-print-arg-env Hello
environ:
ENV2=World
ENV1=!
...

There are two obvious issues with this application:

  • The output indicates that the memory region of the application is not protected.
  • The arguments and environment variables are still under the control of a system software adversary.

Protecting Arguments and Environment Variables

If the arguments and environment variables are passed in from a native shell or passed via the operating system, an adversary could change these arguments and environment variables. We protect the confidentiality and integrity of the arguments and environment variables by defining a security policy (aka session) in which this program executes. The policy defines the environment variables and arguments and we can protect these arguments from being accessed by adversaries. SCONE CAS (Configuration and Attestation Service) is the policy engine that enforces these policies.

Without CAS: No Confidentiality and Integrity of Arguments and Environment Variables

To protect the arguments and environment of a confidential application and to ensure that the program is indeed executed inside an enclave, we need to run the program in the context of CAS policy.**

You might execute this tutorial on a computer without SGX access. As we have shown above, the program issues a warning that SCONE cannot protect the integrity nor the confidentiality of this program. SCONE CAS attests the program and it can determine if the program runs in simulation mode or hardware mode. A policy can define that only a program needs to run in hardware mode and in production mode, i.e., no access to enclave by debugger is possible., before it gets access to its configuration data, i.e., arguments, environment variables, and secrets.

SGX Simulation mode:

Note that if an application runs in simulation mode, its secrets are not protected. To protect secrets, we need to run in hardware mode.

Secure Arguments and Environment Variables

In the next step, we control the environment variables and the arguments with the help of CAS. The SCONE runtime is part of the confidential application: it is a mediator between the operating system and the application. It also connects to CAS to get the application attested and in return, it gets the configuration for the application.

How does the SCONE runtime connect with CAS?

Only when the environment variable SCONE_CONFIG_ID is set, the SCONE runtime will contact CAS. It will use the address stored in SCONE_CAS_ADDR. If SCONE_CAS_ADDR is not set, the runtime will contact host cas``. The SCONE runtime will also contactSCONE_LAS_ADDRfor local attestation. IfSCONE_LAS_ADDRis not set, we contact by default hostlas`.

You can use one of our public CAS instances at domain scone-cas.cf:

export SCONE_CAS_ADDR=scone-cas.cf

In this first step of the tutorial, we do not attest the program, i.e., we perform neither local nor remote attestation: we explicitly switch this off in the policy.

We can now create a new policy on the CAS as follows. Each policy must have a unique name. We generate a unique name using two random numbers:

export SESSION=secure-arguments-example-sim-mode-$RANDOM-$RANDOM

The git repository contains already a policy in file session.yaml. We could manually create this security policy as follows:

cat > session.yaml <<EOF
# Name of the session - this must be unique!
# The creation will fail in case session already exists
name: $SESSION
# Policies are versioned using semantic versioning. For this example,
# we use version 0.3.10
version: 0.3.10

# the security section defines how to attest applications
security:

# We disable attestation first since we might not have
# access to SGX or a LAS, see
#    https://sconedocs.github.io/CAS_session_lang_0_3/#disabling-attestation
  attestation:
    mode: none

services:
   - name: scone-print-arg-env
     command: ./scone-print-arg-env arg1 arg2 arg3
     environment:
        SCONE_MODE: auto
        env1: running
        env2: in
        env3: enclave
     pwd: /
EOF

To be able to upload this policy to CAS, we first need to attest CAS, i.e., we check that this indeed a CAS running inside of an enclave and running the expected code and being signed by the expected entity. We can achieve this by executing

scone cas attest $SCONE_CAS_ADDR 

This will fail by saying that this public CAS executes in debug mode, i.e. an adversary could attach to this CAS with a debugger. Hence, we need to tolerate this. We can determine the right flags for attesting CAS by executing first:

scone cas attest --help

We actually need to tolerate quite a few things before the CLI accepts that this CAS and adds it to the list of CAS instances:

scone cas attest $SCONE_CAS_ADDR --only_for_testing-debug --only_for_testing-ignore-signer --only_for_testing-trust-any --accept-configuration-needed --accept-sw-hardening-needed --accept-group-out-of-date

We can list the CAS instances that were attested by executing

scone cas list

In this case, it would output:

* scone-cas.cf: https://scone-cas.cf:8081/

The * indicates that scone-cas.cf is now the default CAS and that it was attested.

For running applications in production mode, you would run your own CAS instance in production mode on an up-to-date computer.

Creating a security policy (aka session)

We can create a new policy with the command scone session create. The available flags for creating a policy can be printed via flag --help:

scone session create --help

We can set the name of the policy in three different ways:

  • --name $POLICY_NAME overwrites the policy name with the value of the environment variable POLICY_NAME
  • -e SESSION=$SESSION_NAME overwrites the policy name with the value of the environment variable SESSION_NAME
  • --use-env uses the value of the environment variable SESSION to set the policy name.

Note that if none of the above options is used, the session creation will fail with the error:

Caused by: Parsing session from session template file 'session.yaml' failed
Caused by: name: Value for variable $SESSION is missing at line 3 column 7

This will also be the case if we replace the policy name with flag --name since the environment variables are first replaced before the CLI overwrites the policy name with option --name.

When defining both --use-env and -e SESSION=$SESSION_NAME, option -e has priority over the environment variables imported via --use-env.

Now we can create the policy on CAS as follows:

export PREDECESSOR=$(scone session create --use-env session.yaml)

The PREDECESSOR contains the hash value of the policy. This results in an output similar to this:

echo $PREDECESSOR
39ae54974c6892f7a3ee1492175fc092f50b31f9ed9f45e721125fcc332ef3c2

Reading a Policy

To verify that the policy was indeed created, we can read the policy back from CAS as follows:

scone session read $SESSION

This will result in an output like this:

version: 0.3.10
name: secure-arguments-example-sim-mode-1740-30164
predecessor: null
services:
- name: scone-print-arg-env
  environment:
    SCONE_MODE: auto
    env1: running
    env2: in
    env3: enclave
  command: ./scone-print-arg-env arg1 arg2 arg3
  pwd: /
  persistency: None
access_policy:
  read:
  - CREATOR
  update:
  - CREATOR
security:
  attestation:
    mode: none
creator: |
  -----BEGIN CERTIFICATE-----
  MIIBUDCB+KADAgECAghP28D+hc0/8DAKBggqhkjOPQQDAjAhMR8wHQYDVQQDDBZy
  Y2dlbiBzZWxmIHNpZ25lZCBjZXJ0MCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAx
  MDAwMDAwWjAhMR8wHQYDVQQDDBZyY2dlbiBzZWxmIHNpZ25lZCBjZXJ0MFkwEwYH
  KoZIzj0CAQYIKoZIzj0DAQcDQgAE6r3FIds+eewO6SPaw2qz+qUAMrEzLCIlCj/O
  0NuYRdJ3Gg7zBPqQCzp5JuK+AncSDYes95HLvvh69mDowB5xoaMYMBYwFAYDVR0R
  BA0wC4IJc2NvbmUgY2xpMAoGCCqGSM49BAMCA0cAMEQCICm2edJloMbamD51dZGP
  cMv8WRdOFyFelHZOqVXIN6U4AiARU2Tal14Gj2HuTPERMTDTNNk3WAHRM/lSbqgH
  t8QUrg==
  -----END CERTIFICATE-----

We can see that the session was extended by some extra keys - we can see the new keys access_policy and creator:

  • The access_policy determines who can read or updated this policy.
  • The creator is the certificate of the creator. Only an entity that knows the private key can read or update this session.

The public/private key pair is automatically generated by the SCONE CLI. One can print this ID by executing:

scone self show-certificate 

This will print the same certificate as we see in the session that we have read (see above).

Note that the private key of the SCONE CLI is in clear text in the filesystem. This is sufficient for development but not for production systems. For production, we recommend to store the keys inside of a CAS instance and provide access to the keys via an authenticator that computes OTPs, i.e., One Time Passwords. We show in a later step on how to do this. Alternatively, we also support hardware keys which support OpenGPG keys and signing.

Executing a program in context of a policy

We will now execute the program with the context of the policy that we created above:

SCONE_CONFIG_ID=$SESSION/scone-print-arg-env /work/scone-print-arg-env

This program executes in simulation mode and is not attested. This is permitted since we set the attestation mode to none. Therefore, the output contains several warnings that we will address in a later step:

[SCONE|WARN] src/enclave/dispatch.c:140:print_runtime_info(): Application runs in SGX simulation mode. Its memory can be read by the untrusted system! This is not secure!
[SCONE|WARN] scone_rrt::implementation::eai rust-crates/scone_rrt/src/implementation/eai.rs:246:Unable to connect to LAS at default 'las' address (Resolution of LAS address las:18766 failed)- enclave won't be able to attest itself
[SCONE|WARN] src/syscall/syscall.c:31:__scone_ni_syscall(): system call: membarrier, number 324 is not supported
[SCONE|WARN] src/process/init.c:415:__scone_apply_secure_config(): Ignoring `SCONE_PWD` environment variable and host provided process working directory.
    Applying process working directory from service's session configuration (/)
argv: ./scone-print-arg-env arg1 arg2 arg3
environ:
env3=enclave
env1=running
SCONE_MODE=auto
env2=in

We see that the arguments and the environment variables are defined as defined in the policy. Defining arguments on the command line has no impact:

env1=we env2=cannot env3=overwrite env4=environment SCONE_CONFIG_ID=$SESSION/scone-print-arg-env /work/scone-print-arg-env variables

will result in the same output:

argv: ./scone-print-arg-env arg1 arg2 arg3
environ:
env3=enclave
env1=running
SCONE_MODE=auto
env2=in

Note that the command name is the one of the policy: we specified command name ./scone-print-arg-env but we execute command /work/scone-print-arg-env. When switching on attestation, we ensure that the hash value of the command is correct and we do not check at what location the command is stored in the filesystem.

Changing the policy

We need to change the policy in case we want to change the arguments, the environment variables or if we want to enable attestation. Let us first change the policy.

cat > session2.yaml <<EOF
# Name of the session - this must be unique!
# The creation will fail in case session already exists
name: $SESSION
# Policies are versioned using semantic versioning. For this example,
# we use version 0.3.10
version: 0.3.10

# the security section defines how to attest applications
security:

# We disable attestation first since we might not have
# access to SGX or a LAS, see
#    https://sconedocs.github.io/CAS_session_lang_0_3/#disabling-attestation
  attestation:
    mode: none

services:
   - name: scone-print-arg-env
     command: ./scone-print-arg-env our second try using an updated policy
     environment:
        SCONE_MODE: auto
        env1: running
        env2: in
        env3: enclave
     pwd: /
EOF

We can now create a new version of the session by using command scone session update to update the session:

export PREDECESSOR2=$(scone session update session2.yaml)
echo $PREDECESSOR2

When executing the program again, i.e., executing

SCONE_CONFIG_ID=$SESSION/scone-print-arg-env /work/scone-print-arg-env

we see that the updated policy is used, i.e., the new output is:

...
argv: ./scone-print-arg-env our second try using an updated policy
environ:
SCONE_MODE=auto
env2=in
env3=enclave
env1=running

We can now read the policy again as follows:

scone session read $SESSION

The output contains a new key predecessor:

version: 0.3.10
name: secure-arguments-example-sim-mode-28845-15903
predecessor: fa0ae54922f783c02affca2ff88101683abea458c1041819e9f002ebe2fa94c1
services:
...

We can see that the predecessor contains the same field as the hash value that we stored in environment variable PREDECESSOR.