Skip to content

Sconify Container Image (Standard Edition)

Prerequisites

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 sconify_image are:

  • 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:

  1. SCONE File Protection
  2. SCONE Binary File System

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:

Sconify Image Workflow

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: my-repo/my-encrypted-flask.

NATIVE_IMAGE="my-repo/my-native-flask"
ENCRYPTED_IMAGE="my-repo/my-encrypted-flask"

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: my-workflow-namespace-$RANDOM.

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.

determine_sgx_device
Typically, we use the SCONE CLI to attest the CAS and upload sessions. Yet, Sconify Image executes these tasks automatically.

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: --scone-signer=PRIVATE_KEY_PATH

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 CAS_ADDR and 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:

Sconify Image Workflow Binary FS

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 --plain options.

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 (~/.cas/config.json):

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 ...
We can then use the config.json in 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 /build-resources:

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 ...
With these resources, we may rebuild the image ourselves. These resources are invaluable for debugging images created by Sconify Image.

CI/CD Workflow

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:

Command

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 --command=SESSION_CMD.

Environment Variables

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 --env="KEY=VALUE".

Working Directory

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.

Volumes

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 PATH. To simply create a volume:

--volume="my_vol:/vol/simple-vol"

Optionally, volumes can be either imported or exported from or to other sessions, using FROM_SESSION and TO_SESSION, respectively. To import a volume my_in_vol from session import-session, we can use:

--volume="my_in_vol:/vol/in-vol:import-session:"
To export the volume my_out_vol to session export-session, we can use:
--volume="my_out_vol:/vol/out-vol/::export-session"
Exporting to multiple sessions is also possible:
--volume="my_many_out_vol:/vol/many-out-vol::policy-1:policy-2:policy-3"

Scone Signer

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

Supported Options

Option Description
REQUIRED
--from=NATIVE_IMAGE name of the native image (which this script encrypts)
--to=TO_IMAGE name of encrypted image (image output of this script)
--binary=BINARY binary of the native image (which is to be sconified)
--name=SESSION name of the CAS policy session
OPTIONAL
--base=BASE_IMAGE set the base image used to generate the encrypted image (default=alpine:3.10)
--command=SESSION_CMD 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)
--env="KEY=VALUE" set additional env variables (besides those in the native image) to include in the session
--cli=CLI_IMAGE set the SCONE CLI image (default=sconecuratedimages/sconecli:sconify-image)
--service-name=SERVICE_NAME set the name of the service within the SESSION (default: service). Is used for SCONE_CONFIG_ID=SESSION/service
--dir=DIRECTORY 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'
--plain=DIRECTORY copy directories that are not encrypted to the TO_IMAGE file system
--plain-file=FILE copy files that are not encrypted to the TO_IMAGE file system
--volume=NAME:PATH[:[FROM_SESSION][:TO_SESSION_1]:...[:TO_SESSION_N]] 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)
--cas=CAS_ADDR set the name of the CAS_ADDR (default=5-2-0.scone-cas.cf)
--cas-debug permit CAS to be in debug mode
--las=LAS_ADDR set the name of the LAS service (default=localhost:18766)
--trace=TRACE_FILE use tracefile to create encrypted files
--template=TEMPLATE_FILE file containing policy template (default=/usr/local/bin/session-template.yml)
--session=SESSION_FILE file that will contain the session (default=session.yml)
--secrets=FILE policy section that defines secrets (see https://sconedocs.github.io/CAS_session_lang_0_3/#secrets)
--injectedfiles=FILE policy section that defines the injected files (see https://sconedocs.github.io/CAS_session_lang_0_3/#secret-injection-files)
--namespace=NAMESPACE namespace of this session (default=RANDOM)
--create-namespace create namespace (instead of using existing namespace)
ADVANCED
DISABLE DEFAULTS
--disable-binary-symlink-detection for supported services, disable the automatic addition of a symlink to the BINARY in the TO_IMAGE
--disable-copy-shared-object-dependencies disables default inclusion of shared object dependencies of the BINARY on the TO_IMAGE host file system
--disable-copy-service-libraries for supported services, copy the service libraries to the TO_IMAGE
--disable-copy-envvars disables default inclusion of NATIVE_IMAGE environment variables in session
BINARY FILE SYSTEM (binary fs)
--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.
--fs-dir=DIRECTORY add a directory to the binary-fs; add one option per directory
--fs-file=FILE 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.
--crosscompiler=CROSSCOMPILER_IMAGE scone crosscompiler image for compiling binary-fs of executable. Only used when binary-fs is enabled. (default=sconecuratedimages/crosscompilers:alpine3.7)
--disable-binary-fs-defaults disables default inclusion of shared object libraries and service related libraries in the binary fs
SIGNING
--scone-signer=PRIVATE_KEY_PATH 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')
--docker-trust-sign=PRIVATE_KEY_PASSPHRASE 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=INT heap size (default=1G)[SCONE_HEAP]
--stack=INT stack size (default=1M) [SCONE_STACK]
--dlopen=[0|1|2] dlopen: 0 - disable, 1 - enable and require authentication (default), 2 - debug only [SCONE_ALLOW_DLOPEN]
--fork=[0|1] enable fork (default=0) [SCONE_FORK]
DEBUG
--debug enables 'set -x' (commands are printed)
--no-color 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.