Securing containerized applications
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. Izuma 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:
- Izuma Edge LmP target with TPM v2.0 (hardware or software).
- Parsec-enabled Izuma Edge software stack.
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. Izuma 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.
Izuma 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.
Pods 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.
-
Create a file
edge-app.sh
, and save this content:#!/bin/bash set -e printf "Running as %s\n\n" "$(id)" 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.
-
This
Dockerfile
installs parsec-tool and runs the above script:FROM rust:1.67.1-alpine as builder RUN echo "-------- Multi Stage --------" RUN echo "-------- Builder --------" WORKDIR /usr/src/app RUN apk update \ && apk add --no-cache libc-dev protoc bash RUN export PROTOC="/usr/bin/protoc"; cargo install parsec-tool --version 0.5.2 --locked FROM alpine as final_stage RUN echo "-------- Final --------" WORKDIR /usr/src/app COPY ./edge-app.sh . RUN chmod 755 ./edge-app.sh COPY --from=builder /usr/local/cargo/bin/parsec-tool /usr/local/bin RUN chmod 755 /usr/local/bin/parsec-tool RUN apk update \ && apk add --no-cache bash ENTRYPOINT ["/usr/src/app/edge-app.sh"]
-
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
-
Log in to the gateway.
-
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 Izuma Edge, the Parsec services are configured to run by
user=parsec
andgroup=parsec
. Therefore, for containers to access the Parsec service's socket file, the users need to be part of thegroup=parsec
.
Deploy the application
-
On your local workstation, make sure you've installed and configured
kubectl
with your Izuma account credentials. -
Get your gateway's device or node ID using
kubectl get nodes
. Alternatively, you can log in to the gateway and runsudo info
to get the device ID. -
Define the Pod specification:
- 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
- Create
-
Deploy the Pods:
kubectl create -f bob-pod.yaml kubectl create -f alice-pod.yaml
-
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 withuser=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.