Sconify Container Image (Standard Edition)
Please read section Sconify to understand the general sconification workflow first.
sconify_image is a command line tool within a SCONE image that enables the sconification of native images without detailed knowledge of the SCONE framework.
sconify_image transforms a native container image to an encrypted SCONE-enabled image in one step. We call this transformation sconification. These SCONE-enabled images execute their service within an SGX enclave using the SCONE runtime. In particular, the features of
- sconification of the native image binary such that the execution of the sconified binary is performed within an SGX enclave
- encryption and integrity protection of native image files: giving access to the sconified binary only after it is attested by the SCONE platform
- creation of a SCONE policy using sensible defaults (e.g., including native image environment variables and working directory), and with options to customize to use additional features of the SCONE policy language (e.g., SCONE shared volumes, shared secrets, injected files) - ensuring only the sconified binary can access the secured resources, and
- usage of all these features with only a single command - enabling an easy integration into existing CI/CD workflows.
We continue by demonstrating the usage of Sconify Image by sconifying a Python Flask REST API image. We thereby show the two main methods of image sconification with:
Furthermore, we show how
sconify_image facilitates easy modification of the SCONE policy. Finally, we give a detailed technical description of all command line options that the tool supports.
sconify_image currently supports only Alpine container images
This 1-step conversion supports native images based on Alpine Linux only. We plan to support Ubuntu-based images in the very near future too.
Sconify Image Processing TIme
When using the binary-fs,
sconify_image requires a significant amount of computational and memory resources:
sconify_image may take several minutes to sconify an image. Typically, this is not an issue since
sconify_image will run as part of a CI pipeline running on a server with sufficient main memory.
Sconify Image Workflow
SCONE File Protection
Suppose we have a simple Python Flask REST API service which we wish to sconify. We present the general workflow of the sconification process using SCONE File Protection:
As shown, Sconify Image sconifies the native binary, and copies the sconified binary to the encrypted image. Further, Sconify Image encrypts the service's code and data files, and copies these encrypted files to the encrypted image as well. During the sconification of the image, Sconify Image creates a security policy containing metadata to decrypt the encrypted files and to check the files integrity. This policy also contains additional information, e.g., the native images environment and working directory. Sconify Image uploads this policy to an attested SCONE CAS. After Sconify Image finishes this process, we can run a confidential container from the encrypted image. Upon execution, the SCONE CAS attests the service to ensure its trustworthiness. After a successful attestation, the CAS sends the service the secrets specified in the policy, which enable the service to execute as expected.
We continue by showing a possible implementation of this workflow. The Dockerfile of the Flask service may look as follows:
FROM alpine:3.10 ENV LANG C.UTF-8 COPY rest_api.py /app/rest_api.py COPY flask.key /tls/flask.key COPY flask.crt /tls/flask.crt COPY requirements.txt /app/requirements.txt RUN apk add --no-cache openssl ca-certificates pkgconfig wget python3 python3-dev \ && ln -s /usr/bin/pip3 /usr/local/bin/pip \ && ln -s /usr/bin/pip3 /usr/bin/pip \ && pip3 install -r /app/requirements.txt CMD python3 -B /app/rest_api.py
To then use Sconify Image, we must select resources relevant for sconification. We see that the program code is in
/app, and the data files that the service uses are in
/tls. Further, our service runs in Python. Upon inspection, e.g., with
$(which python3), we find that our binary is located at
/usr/bin/python3.7. We specify this information:
PROTECT_DIR_1="/app" PROTECT_DIR_2="/tls" BINARY="/usr/bin/python3.7"
Let us assume that the native image is named
my-repo/my-native-flask. Further, we specify the name for the finished encrypted image for later use:
We also decide on information for using the SCONE infrastructure. We select a CAS to upload our policy, in this case the public CAS
5-2-0.scone-cas.cf. We additionally specify the name of a LAS. Further, we set a name for the session we wish to upload:
my-flask-session, as well as a name for the flask service within the session:
my-flask-service. And finally, we set a namespace CAS namespace to bundle this session with further sessions we may create in our workflow:
CAS_ADDR="5-2-0.scone-cas.cf" LAS_ADDR="localhost:18766" SERVICE="my-flask-service" SESSION="my-flask-session" NAMESPACE="my-workflow-namespace-$RANDOM"
We now require the SCONE environment variables with which we run our service:
SCONE_HEAP="1G" SCONE_STACK="4M" SCONE_ALLOW_DLOPEN="2"
Finally, to attest the CAS and upload a policy, we need access to the SGX device. Hence, we determine the SGX device (see determining SGX device). The SGX device allows Sconify Image to attest the SCONE CAS.
With all this information, we can run the Sconify Image tool:
docker run --rm --device="$SGXDEVICE" \ -v /var/run/docker.sock:/var/run/docker.sock \ registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image \ sconify_image --from="$NATIVE_IMAGE" --to="$ENCRYPTED_IMAGE" --cas-debug \ --dir="$PROTECT_DIR_1" --dir="$PROTECT_DIR_2" --binary="$BINARY" \ --cas="$CAS_ADDR" --las="$LAS_ADDR" \ --service-name="$SERVICE" --name="$SESSION" --namespace="$NAMESPACE" \ --create-namespace \ --heap="$SCONE_HEAP" --stack="$SCONE_STACK" --dlopen="$SCONE_ALLOW_DLOPEN"
Running in Production Mode
For production, we must remove
--cas-debug to ensure the CAS runs in production mode, and sign the binary with a production key:
Upon successful execution, the tool then returns the name of the session (
"$NAMESPACE/$SESSION"), and the encrypted image under the name
"$ENCRYPTED_IMAGE" will have been created. The encrypted image also contains the
LAS_ADDR, as well as the
SCONE_CONFIG_ID. As such, we can then simply run the service, and it will utilize existing SCONE infrastructure without the need for further specification:
docker run --rm --device="$SGXDEVICE" $ENCRYPTED_IMAGE
SCONE Binary File System
Using the SCONE Binary File System (binary-fs), we can embed the service's files into the binary. This embedding extends the protection of the SGX enclave to the binary-fs. Further, the enclave's measurement (MRENCLAVE) then reflects the file system state. As such, the usage of binary-fs changes the process of Sconify Image sconification. The updated process is as follows:
As pictured, we must specify all files the service may access. This includes service libraries. As we use Python, Sconify Image includes the Python library be default. Thus, we must only make minimal changes to the command from before. To enable the binary-fs sconifcation mode, we must set
--binary-fs. To specify directories to include in the binary-fs, we select them using
--fs-dir. We can select individual files to include as well with
--fs-file. The modified Sconify Image command could look as follows:
docker run --rm --device="$SGXDEVICE" \ -v /var/run/docker.sock:/var/run/docker.sock \ registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image \ sconify_image --from="$NATIVE_IMAGE" --to="$ENCRYPTED_IMAGE" --cas-debug \ --fs-dir="$PROTECT_DIR_1" --fs-dir="$PROTECT_DIR_2" --binary="$BINARY" \ --cas="$CAS_ADDR" --las="$LAS_ADDR" \ --service-name="$SERVICE" --name="$SESSION" --namespace="$NAMESPACE" \ --create-namespace \ --heap="$SCONE_HEAP" --stack="$SCONE_STACK" --dlopen="$SCONE_ALLOW_DLOPEN" \ --binary-fs
Service Library Inclusion
For supported services, we include service libraries into the binary file system by default. To view supported service, run
docker run --rm registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image sconify_image --help
In both Sconification variations, Sconify Image copies shared object dependencies of the binary into the encrypted image by default. Otherwise, the image container cannot execute the binary, as the shared object dependencies can not be loaded if they do not exist in the container's host file system. With binary-fs, the shared object dependencies loaded by the binary do not have to be included in the binary-fs. To include dependencies into the host file system of the encrypted image, we can use the
Acquiring Policy Access
Before uploading a session to the SCONE CAS, Sconify Image generates a private key. Sconify Image then generates a corresponding public key. While uploading the session to the SCONE CAS, Sconify Image sends the CAS this public key. Sconify Image can then use this public / private key pair to identify itself to the CAS. The CAS binds the public key to Sconify Image's session, such that only holders of the corresponding private key may read and update the session. We may wish to read and update our session after Sconify Image is finished with the sconification process. Thus, we can retrieve Sconify Image's private key. We achieve this extraction by using a docker volume when running Sconify Image. We thereby mount the volume to the directory where Sconify Image stores, among other CAS relevant information, the private key (
docker run --rm --device="$SGXDEVICE" \ -v $PWD/private-sconify-image-key:/root/.cas \ # PRIVATE KEY VOLUME -v /var/run/docker.sock:/var/run/docker.sock \ registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image \ sconify_image ...
config.jsonin other containers to read and update the session:
docker run --rm --device="$SGXDEVICE" \ -v $PWD/private-sconify-image-key:/root/.cas my-scone-image
Acquire Sconify Image Resources
During the sconification process, Sconify Image generates the encrypted image's Dockerfile and session. To collect these resources, we add a volume to Sconify Image. Sconify Image stores resources in
docker run --rm --device="$SGXDEVICE" \ -v $PWD/sconify-image-resources:/build-resources \ # RESOURCE COLLECTION VOLUME -v /var/run/docker.sock:/var/run/docker.sock \ registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image \ sconify_image ...
In a typical workflow, one would perform the sconification of an image as part of the CI/CD pipeline: One would test the native image first, sconify the image and test the sconified image again. After a successful test, the binary would be pushed to a container image repository.
Policy Extension Options
Sconify Image enables easy extension of the security policy created in the sconification process. Currently, Sconify Image supports following security policy extensions:
By default, Sconify Image sets the command in the security policy as the command in the native image. The container image executes this command upon a successful attestation by the CAS. To set a custom command, we can use
By default, Sconify Image copies all environment variables from the native image to the security policy. Hence, Sconify Image protects the native environment variables. We can disable this default with
--disable-copy-envvars. To specify additional environment variables for the security policy, we can use
By default, Sconify Image copies the working directory of the native image into the security policy. As such, upon execution, the working directory of the service will be the working directory set in the native image. The working directory's inclusion into the policy also protects it from external entities.
With the option
--volume=NAME:PATH:[[FROM_SESSION]:[TO_SESSION]], we can easily create and access SCONE volumes. We set the volume name with
NAME and the mount path into the encrypted image with
To simply create a volume:
Optionally, volumes can be either imported or exported from or to other sessions, using
TO_SESSION, respectively. To import a volume
my_in_vol from session
import-session, we can use:
export-session, we can use:
Sconify Image can also sign binaries of images during sconification, using the scone signer. Only signed binaries may run in production mode. Signing binaries requires an Intel verified private key. To use the scone signer, add the option
--scone-signer=PRIVATE_KEY_PATH, whereby the
PRIVATE_KEY_PATH is the patch to the private key accessible by the container. We give the container access with a volume. A corresponding command can look as follows:
docker run --rm --device="$SGXDEVICE" \ -v $PWD/my-private-key.pem:/my-private-key.pem \ # SUPPLY CONTAINER THE KEY -v /var/run/docker.sock:/var/run/docker.sock \ registry.scontain.com:5050/sconecuratedimages/sconecli:sconify-image \ sconify_image ... --scone-signer=/my-private-key.pem
||name of the native image (which this script encrypts)|
||name of encrypted image (image output of this script)|
||binary of the native image (which is to be sconified)|
||name of the CAS policy session|
||set the base image used to generate the encrypted image (default=alpine:3.10)|
||set the command in the session which is to be securely executed, e.g., '/my-app/my-binary /my-app/secret-foo.txt'. (Default: ENTRYPOINT + CMD of native image)|
||set additional env variables (besides those in the native image) to include in the session|
||set the SCONE CLI image (default=sconecuratedimages/sconecli:sconify-image)|
||set the name of the service within the SESSION (default: service). Is used for SCONE_CONFIG_ID=SESSION/service|
||add directory to encrypt; add one option per directory. Will be added to the BASE_IMAGE file system. NOT compatible with '--trace' and '--binary-fs'|
||copy directories that are not encrypted to the TO_IMAGE file system|
||copy files that are not encrypted to the TO_IMAGE file system|
||set a volume with name NAME at path PATH in the session (https://sconedocs.github.io/CAS_session_lang_0_3/#volumes). Optionally EITHER import volume from FROM_SESSION OR export to TO_SESSION. Multiple export volumes may be specified. If FROM_SESSION empty, only TO_SESSSION will be used (e.g. name:path::to_session)|
||set the name of the CAS_ADDR (default=5-2-0.scone-cas.cf)|
||permit CAS to be in debug mode|
||set the name of the LAS service (default=localhost:18766)|
||use tracefile to create encrypted files|
||file containing policy template (default=/usr/local/bin/session-template.yml)|
||file that will contain the session (default=session.yml)|
||policy section that defines secrets (see https://sconedocs.github.io/CAS_session_lang_0_3/#secrets)|
||policy section that defines the injected files (see https://sconedocs.github.io/CAS_session_lang_0_3/#secret-injection-files)|
||namespace of this session (default=RANDOM)|
||create namespace (instead of using existing namespace)|
||for supported services, disable the automatic addition of a symlink to the BINARY in the TO_IMAGE|
||disables default inclusion of shared object dependencies of the BINARY on the TO_IMAGE host file system|
||for supported services, copy the service libraries to the TO_IMAGE|
||disables default inclusion of NATIVE_IMAGE environment variables in session|
|BINARY FILE SYSTEM (binary fs)|
||use the SCONE binary file system to embed the file system into the binary; ONLY SUPPORTS PIE COMPILED BINARIES. See https://sconedocs.github.io/binary_fs/ for more information.|
||add a directory to the binary-fs; add one option per directory|
||specify certain files to include in the binary fs. Useful if including the file's directory makes the binary fs too large, which increases build time.|
||scone crosscompiler image for compiling binary-fs of executable. Only used when binary-fs is enabled. (default=sconecuratedimages/crosscompilers:alpine3.7)|
||disables default inclusion of shared object libraries and service related libraries in the binary fs|
||sign the binary for production mode (uses other arguments from environment). Private key must be a RSA-3072-Key with modulo 3 (generated by 'openssl genrsa -3 -out key.pem 3072')|
||sign the image with docker trust signer (requires docker trust private key on platform and corresponding public key to be added to target repo of TO_IMAGE). WILL EXECUTE A DOCKER PUSH.|
|SCONE ENV VARS|
||heap size (default=1G)[SCONE_HEAP]|
||stack size (default=1M) [SCONE_STACK]|
||dlopen: 0 - disable, 1 - enable and require authentication (default), 2 - debug only [SCONE_ALLOW_DLOPEN]|
||enable fork (default=0) [SCONE_FORK]|
||enables 'set -x' (commands are printed)|
||disable colored output (useful for execution in scripts)|
Using Custom Files
To use files in Sconify Image that are not in the native image, e.g., template file, they must be mounted into the Sconify Image container. The path to the file within the container must then be specified in the respective option.