Mistake on this page? Email us

Securing containerized applications

Note: These steps don't work on the Raspberry Pi 3 Model B+.

With a growing number of edge applications running in containers, it is essential to safekeep the sensitive runtime data and perform cryptographic operations in a secure, confined environment on the gateway. Pelion Edge uses Trust Platform Module (TPM) v2.0 and Parsec to fulfill this requirement. This document demonstrates how to secure your edge container application. It also provides the best practices to maintain the integrity and proper isolation of your container assets.

Prerequisites

Before you can secure your containerized applications, you must have a:

Using TPM and Parsec to secure your application

Parsec abstracts the functionality of the various crypto service providers and defines a common set of APIs exposed through a Unix domain socket. Your application can access these APIs directly or by using their client library, which is available in Rust and Go.

Parsec also provides multitenancy and an access control feature, wwith which it creates an isolated view of key storage and cryptographic services for each client application. Parsec can only achieve this isolation if every client application can present a unique and stable identity to the security service. Pelion Edge defaults to using the Unix Peer Credentials Authenticator method to share the client application's Unix user identifier (UID) with the Parsec service. This authenticator then verifies that the UID sourced from the peer credentials matches the one self-declared in the request. The Parsec service always uses the client's application identity string to maintain this separation.

Pelion Edge also restricts access to Parsec's Unix domain socket by setting the correct file permissions and ownership. Together, these methods ensure the storage of your application assets, such as keys, is secure and separated on a per-client basis: Assets created by one client can't be accessed by another.

With this guide, you'll deploy two Pods running the same container application but that have different user permissions. This demonstrates:

  • The interaction with TPM using Parsec APIs from within the container.
  • The isolation of your application's secure assets on the basis of the container's UID.

Diagram showing Pods with different user permissions running the same container applicationPods with different user permissions running the same container application

Build a Docker image

Note: This document explains how to build a Docker image directly on the gateway. However, you can also crosscompile the Docker image for your target architecture using Docker buildx.

  1. Create a file edge-app.sh, and save this content:

    #!/bin/bash
    set -e
    printf "Running as `id`\n\n"
    if parsec-tool ping | grep '1.0'; then 
        printf "Parsec service is reachable!\n\n"
    
        printf "List the keys managed by this user..\n\n"
        parsec-tool list-keys
        if parsec-tool list-keys |& grep "No keys currently available"; then
            # No keys found in TPM which are either created or accessed by this user
            # Create ECC key pair
            printf "Create an ECC key pair..\n\n"
            parsec-tool create-ecc-key --key-name `id -u`-key-$((1 + $RANDOM % 10))
            parsec-tool list-keys
        fi
    fi
    

    The example application uses Parsec Tool, a command-line program built using the Parsec's Rust client library. This verifies whether the application running inside the container has correct user permissions to access the Parsec service. If it finds no keys, it creates an ECC key pair.

  2. This Dockerfile installs parsec-tool and runs the above script:

    FROM rust:1.51-alpine as builder
    
    WORKDIR /usr/src/app
    
    COPY ./edge-app.sh .
    RUN chmod 755 ./edge-app.sh
    
    RUN apk update \
        && apk add --no-cache libc-dev protoc bash
    
    RUN export PROTOC="/usr/bin/protoc"; cargo install parsec-tool --locked
    
    ENTRYPOINT ["/usr/src/app/edge-app.sh"]
    
  3. Build the Docker image:

    docker build -t edge-app:latest -f ./Dockerfile .
    

    To verify the Docker image works, you can run the container directly using:

    docker run -v /run:/run edge-app:latest
    

Prepare the gateway

  1. Log in to the gateway.

  2. To emulate a real world use case, create two temporary users with a strong password:

    sudo useradd -G parsec -e $(date --date='1 week' --rfc-3339=date) -u 1250 bob
    sudo passwd bob
    
    sudo useradd -G parsec -e $(date --date='1 week' --rfc-3339=date) -u 1251 alice
    sudo passwd alice
    

    In Pelion Edge, the Parsec services are configured to run by user=parsec and group=parsec. Therefore, for containers to access the Parsec service's socket file, the users need to be part of the group=parsec.

Deploy the application

  1. On your local workstation, make sure you've installed and configured kubectl with your Pelion account credentials.

  2. Get your gateway's device or node ID using kubectl get nodes. Alternatively, you can log in to the gateway and run sudo info to get the device ID.

  3. Define the Pod specification:

    1. Create bob-pod.yaml:
    • Replace YOUR-NODE-ID-HERE with your gateway's deviceID.
    • Assuming, the Docker image is available on the gateway.
    • Mount /run as /run/parsec contains the Parsec socket file.
    apiVersion: v1
    kind: Pod
    metadata:
      name: bob-edge-app-**YOUR-NODE-ID-HERE**
    spec:
      automountServiceAccountToken: false
      hostname: edge-app-bob
      nodeName: **YOUR-NODE-ID-HERE**
      securityContext:
        runAsUser: 1250
        runAsGroup: 1206
      containers:
      - name: edge-app
        image: edge-app:latest
        imagePullPolicy: Never
        volumeMounts:
          - mountPath: /run
            name: parsec-sock
      restartPolicy: OnFailure
      volumes:
      - name: parsec-sock
        hostPath:
          path: /run
    

    Similarly, create alice-pod.yaml:

    apiVersion: v1
    kind: Pod
    metadata:
      name: alice-edge-app-**YOUR-NODE-ID-HERE**
    spec:
      automountServiceAccountToken: false
      hostname: edge-app-alice
      nodeName: **YOUR-NODE-ID-HERE**
      securityContext:
        runAsUser: 1251
        runAsGroup: 1206
      containers:
      - name: edge-app
        image: edge-app:latest
        imagePullPolicy: Never
        volumeMounts:
          - mountPath: /run
            name: parsec-sock
      restartPolicy: OnFailure
      volumes:
      - name: parsec-sock
        hostPath:
          path: /run
    
  4. Deploy the Pods:

    kubectl create -f bob-pod.yaml
    kubectl create -f alice-pod.yaml
    
  5. To view the logs:

    kubectl logs -f **user**-edge-app-**YOUR-NODE-ID-HERE**
    

    For example, below is the application log when you run the container with user=bob permissions and the snippet after that is with user=alice permissions:

    Running as uid=1250 gid=1206(parsec)
    1.0
    Parsec service is reachable!
    List the keys managed by this user..
    [INFO ] Service wire protocol version
    [INFO ] No keys currently available.
    [INFO ] No keys currently available.
    Create an ECC key pair..
    [INFO ] Creating ECC key...
    [INFO ] Key "1250-key-1" created.
    [INFO ] Available keys:
    * 1250-key-1 (Mbed Crypto provider, EccKeyPair { curve_family: SecpR1 }, 256 bits, permitted algorithm: AsymmetricSignature(Ecdsa { hash_alg: Specific(Sha256) }))
    
    Running as uid=1251 gid=1206(parsec)
    [INFO ] Service wire protocol version
    1.0
    Parsec service is reachable!
    List the keys managed by this user..
    [INFO ] No keys currently available.
    [INFO ] No keys currently available.
    Create an ECC key pair..
    [INFO ] Creating ECC key...
    [INFO ] Key "1251-key-3" created.
    [INFO ] Available keys:
    * 1251-key-3 (Mbed Crypto provider, EccKeyPair { curve_family: SecpR1 }, 256 bits, permitted algorithm: AsymmetricSignature(Ecdsa { hash_alg: Specific(Sha256) }))
    

    This shows the application's secure assets and crypto operations are isolated by the client's identity. If the socket file path isn't mounted on the container or the permissions aren't set correctly (try setting random groupID runAsGroup=1345), then you can't interface with Parsec and, in turn, TPM.

Best secure practices

When deploying the edge application using container orchestration, always state the securityContext in the Pod specifications. This restricts the privileges and access control of an application to system resources and establishes a clean environment less prone to security leaks. Also, make sure you give system resources, such as files or directories, good permissions and ownerships.