Skip to content

PortainerCC Create Template

Sören Langenberg edited this page Jul 6, 2023 · 9 revisions

Prerequisites

Make sure you installed Docker and your machine is SGX ready.

This guide will start from a running Gramine Application.

I will use the SGX-ready MariaDB provided by enclaive as a starting point.

Step 1: Edit the gramine manifest

At first we need to update the Gramine manifest to make use of the MarbleRun Coordinator.

We need to change the loader entrypoint to the premain executable which is provided by MarbleRun.

libos.entrypoint = "/premain/premain-libos"

Then we will use the argv0_override to point to our "old" entrypoint.

loader.argv0_override = "/app/launcher"

We will remove the other argv parameter since it will be passed by the coordinator later and add some more needed parameter, which used to pass env vars.

loader.env.EDG_MARBLE_TYPE = { passthrough = true }
loader.env.EDG_MARBLE_COORDINATOR_ADDR = { passthrough = true }
loader.env.EDG_MARBLE_UUID_FILE = { passthrough = true }
loader.env.EDG_MARBLE_DNS_NAMES = { passthrough = true }

After that we need to add the premain to our fs.mounts. I will move the var/lib/mysql:data mapping also and place my later used key name (and remove insecure encryption keys form the manifest).

fs.mounts = [
    ...
    #premain libos
    { path = "/premain",          uri = "file:/premain" },

    #volumes
    { path = "/var/lib/mysql",    uri = "file:/data", type = "encrypted", key_name = "redis_data" },
]

It is also important to use dcap attestation and provide a svn and prodid.

sgx.remote_attestation = "dcap"
sgx.isvprodid = 13
sgx.isvsvn    = 1

In the trusted files section we need to declare the premain.

sgx.trusted_files = [
    ...
    "file:/premain/premain-libos",
]

We also need to allow some files to resolve networking and uuid. (allowed files can be a security issue)

sgx.allowed_files = [
  "file:/etc/ethers",
  "file:/etc/hosts",
  "file:/etc/localtime",
  "file:/etc/resolv.conf",
  "file:/etc/host.conf",

  # MARBLERUN: allow the marble's uuid file
  "file:uuid"
]

Our Gramine manifest is ready. You can check out mine here.

Step 2: Add premain into image

In the next step we are building our Docker image. I will use the existing one as a starting point and will add the following lines to add the premain.

#premain
FROM ghcr.io/edgelesssys/edgelessrt-dev:latest AS marblerun

RUN git clone --branch=master --depth=1 https://github.com/edgelesssys/marblerun \
    && cd ./marblerun/ \
    && mkdir ./build/ \
    && cd ./build/ \
    && cmake .. \
    && make -j

# and in the final stage copy it:

COPY --from=marblerun /marblerun/build/premain-libos /premain/

I will also take the chance and update the gramine build command to use Docker secrets. And we also need to set up the PCCS URLs.

RUN --mount=type=secret,id=signingkey gramine-manifest -Darch_libdir=/lib/x86_64-linux-gnu mariadb.manifest.template mariadb.manifest \
    && gramine-sgx-sign --key "/run/secrets/signingkey" --manifest mariadb.manifest --output mariadb.manifest.sgx \
    && gramine-sgx-get-token -s ./mariadb.sig -o attributes \
    && cat ./attributes \
    && sed -i 's,https://localhost:8081/sgx/certification/v3/,https://172.17.0.1:8081/sgx/certification/v3/,g' /etc/sgx_default_qcnl.conf \
    && sed -i 's,"use_secure_cert": true,"use_secure_cert": false,g' /etc/sgx_default_qcnl.conf 

We can build the image with the following command:

DOCKER_BUILDKIT=1 docker build --secret id=signingkey,src=/signing/enclave-key.pem --tag marcely0/pcc-mariadb-demo -f Dockerfile.marble .

After building, we need to retrieve the unique id (mrenclave, mrsigner) if you didnt catch them during build. In order to catch the values easier you can capture the whole build process into a log file using &> build.log at the end of the command from above. I will just override the entrypoint of the image and get the token.

docker run -it --entrypoint /bin/bash --device=/dev/sgx/enclave:/dev/sgx/enclave --device=/dev/sgx/enclave:/dev/sgx_enclave --device=/dev/sgx_provision:/dev/sgx_provision marcely0/pcc-mariadb-demo

After that i will use the two values to lable the image to make it work with portainercc. (Just use a simple Dockerfile to add another layer)

FROM marcely0/pcc-mariadb-demo
LABEL pcc.mrenclave=123 pcc.mrsigner=456

Our Image is ready to be used in portainercc.

Step 3: Define a portainercc template

As described in the main wiki, you can overwrite the confidential-templates.json with your self created templates.

Here you can see the one I used for the MariaDB image we just created:

{
    "ImageName": "marcely0/pcc-mariadb-demo",
    "LogoURL": "https://mariadb.com/wp-content/uploads/2019/11/mariadb-logo-vert_blue-transparent.png",
    "TemplateName": "MariaDB-pcc",
    "Inputs": [
      {
        "Label": "Username",
        "Default": "root",
        "Type": "SECRET",
        "SecretName": "init",
        "ReplacePattern": "$$$username$$$"
      },
      {
        "Label": "Password",
        "Default": "pass",
        "Type": "SECRET",
        "SecretName": "init",
        "ReplacePattern": "$$$password$$$"
      },
      {
        "Label": "/data",
        "Default": "mariadb_data",
        "Type": "VOLUME",
        "SecretName": "mariadbDataKey"
      },
      {
        "Label": "/secrets",
        "Default": "mariadbd_secrets",
        "Type": "VOLUME",
        "SecretName": "mariadbSecretsKey"
      },
      {
        "Label": "Port1",
        "Default": "3306",
        "Type": "PORT",
        "PortContainer": "3306",
        "PortType": "tcp"
      }
    ],
    "Secrets": {
      "init": "CREATE OR REPLACE USER $$$username$$$ IDENTIFIED BY '$$$password$$$';\n GRANT ALL PRIVILEGES ON *.* TO  $$$username$$$ ;"
    },
    "ManifestBoilerplate": {
      "ManifestParameters": {
        "Files": {
          "/secrets/init.sql": {
            "Data": "{{ raw .Secrets.init.Private }}",
            "Encoding": "string",
            "NoTemplates": false
          },
          "/dev/attestation/keys/mariadbd_secrets": {
            "Data": "{{ raw .Secrets.mariadbSecretsKey.Private }}",
            "Encoding": "string",
            "NoTemplates": false
          },
          "/dev/attestation/keys/mariadbd_data": {
            "Data": "{{ raw .Secrets.mariadbDataKey.Private }}",
            "Encoding": "string",
            "NoTemplates": false
          }
        },
        "Argv": [
          "/app/mariadbd",
          "--init-file=/secrets/init.sql"
        ]
      },
      "ManifestSecrets": {
        "init": {
          "type": "plain",
          "UserDefined": true
        },
        "mariadbSecretsKey": {
          "Type": "symmetric-key",
          "Size": 128,
          "UserDefined": true
        },
        "mariadbDataKey": {
          "Type": "symmetric-key",
          "Size": 128,
          "UserDefined": true
        }
      }
    }
  }

Lets dive deeper into the scheme.

  • ImageName - Here we will set up the Dockerhub imagename
  • LogoURL - Just a URL to serve an image
  • TemplateName - Visual name of the template
  • Inputs - He we will define the inputs we want the user to define in the frontend
  • Label - the label of the input
  • Default - a default value
  • Type - for now there are 3 types available:
  • SECRET - used with a ReplacePattern to substitude in SecretStrings. SecretName is used to identify the secret.
  • VOLUME - to select a encrypted volume, whereas the SecretName is used to identify where to replace the key in the MarbleRun manifest.
  • PORT - where the PortContainer and PortType has to be set up to be mapped for the input.
  • Secrets - here we can define a secret string, which can hold placeholders to be replaced by user input. (see Input type SECRET)
  • ManifestBoilerPlate - this values are passed like you define them into the MarbleRun manifest.
  • ManifestParameters - we define the Files (Encrypted Files and Keys!) and set the Argv parameters (important since we took them out of the gramine manifest!)
  • ManifestSecrets - We need to define the Secrets the coordinator will store for us.

If you want to dive deeper into the MarbleRun manifest part you can find the documentation here.

Done

After you mounted your templates.json you can hit the confidential template tab and will see your own portainercc templates:

image