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.
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
andRDRND
.
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 contact
SCONE_LAS_ADDRfor local attestation. If
SCONE_LAS_ADDRis not set, we contact by default host
las`.
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 variablePOLICY_NAME
-e SESSION=$SESSION_NAME
overwrites the policy name with the value of the environment variableSESSION_NAME
--use-env
uses the value of the environment variableSESSION
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
.