forked from sc2-sys/deploy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use encrypted container images (sc2-sys#20)
- Loading branch information
1 parent
c4ff04a
commit d91e00d
Showing
11 changed files
with
371 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
apiVersion: serving.knative.dev/v1 | ||
kind: Service | ||
metadata: | ||
name: helloworld-knative | ||
annotations: | ||
"features.knative.dev/podspec-runtimeclassname": "enabled" | ||
spec: | ||
template: | ||
metadata: | ||
labels: | ||
apps.coco-serverless/name: helloworld-py | ||
io.katacontainers.config.pre_attestation.enabled: "false" | ||
spec: | ||
runtimeClassName: kata-qemu-sev | ||
# coco-knative: need to run user container as root | ||
securityContext: | ||
runAsUser: 1000 | ||
containers: | ||
- image: csegarragonz/coco-helloworld-py:encrypted | ||
ports: | ||
- containerPort: 8080 | ||
env: | ||
- name: TARGET | ||
value: "World" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"key-providers": { | ||
"attestation-agent": { | ||
"grpc": "127.0.0.1:50000" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
from base64 import b64encode | ||
from invoke import task | ||
from json import loads as json_loads | ||
from os.path import exists, join | ||
from subprocess import run | ||
from tasks.util.cosign import sign_container_image | ||
from tasks.util.env import CONF_FILES_DIR, K8S_CONFIG_DIR | ||
from tasks.util.guest_components import ( | ||
start_coco_keyprovider, | ||
stop_coco_keyprovider, | ||
) | ||
from tasks.util.kbs import create_kbs_secret | ||
|
||
SKOPEO_VERSION = "1.13.0" | ||
SKOPEO_IMAGE = "quay.io/skopeo/stable:v{}".format(SKOPEO_VERSION) | ||
SKOPEO_ENCRYPTION_KEY = join(K8S_CONFIG_DIR, "image_enc.key") | ||
AA_CTR_ENCRYPTION_KEY = "/tmp/image_enc.key" | ||
|
||
|
||
def run_skopeo_cmd(cmd, capture_stdout=False): | ||
ocicrypt_conf_host = join(CONF_FILES_DIR, "ocicrypt.conf") | ||
ocicrypt_conf_guest = "/ocicrypt.conf" | ||
skopeo_cmd = [ | ||
"docker run --rm", | ||
"--net host", | ||
"-e OCICRYPT_KEYPROVIDER_CONFIG={}".format(ocicrypt_conf_guest), | ||
"-v {}:{}".format(ocicrypt_conf_host, ocicrypt_conf_guest), | ||
"-v ~/.docker/config.json:/config.json", | ||
SKOPEO_IMAGE, | ||
cmd, | ||
] | ||
skopeo_cmd = " ".join(skopeo_cmd) | ||
if capture_stdout: | ||
return ( | ||
run(skopeo_cmd, shell=True, capture_output=True) | ||
.stdout.decode("utf-8") | ||
.strip() | ||
) | ||
else: | ||
run(skopeo_cmd, shell=True, check=True) | ||
|
||
|
||
def create_encryption_key(): | ||
cmd = "head -c32 < /dev/random > {}".format(SKOPEO_ENCRYPTION_KEY) | ||
run(cmd, shell=True, check=True) | ||
|
||
|
||
@task | ||
def encrypt_container_image(ctx, image_tag, sign=False): | ||
""" | ||
Encrypt an OCI container image using Skopeo | ||
The image tag must be provided in the format: docker.io/<repo>/<name>:tag | ||
""" | ||
encryption_key_resource_id = "default/image-encryption-key/1" | ||
if not exists(SKOPEO_ENCRYPTION_KEY): | ||
create_encryption_key() | ||
|
||
# We use CoCo's keyprovider server (that implements the ocicrypt protocol) | ||
# to encrypt the OCI image. To that extent, we need to mount the encryption | ||
# key somewhere that the attestation agent (in the keyprovider) can find | ||
# it | ||
start_coco_keyprovider(SKOPEO_ENCRYPTION_KEY, AA_CTR_ENCRYPTION_KEY) | ||
|
||
encrypted_image_tag = image_tag.split(":")[0] + ":encrypted" | ||
skopeo_cmd = [ | ||
"copy --insecure-policy", | ||
"--authfile /config.json", | ||
"--encryption-key", | ||
"provider:attestation-agent:keyid=kbs:///{}::keypath={}".format( | ||
encryption_key_resource_id, AA_CTR_ENCRYPTION_KEY | ||
), | ||
"docker://{}".format(image_tag), | ||
"docker://{}".format(encrypted_image_tag), | ||
] | ||
skopeo_cmd = " ".join(skopeo_cmd) | ||
run_skopeo_cmd(skopeo_cmd) | ||
|
||
# Sanity check that the image is actually encrypted | ||
inspect_jsonstr = run_skopeo_cmd( | ||
"inspect docker://{}".format(encrypted_image_tag), capture_stdout=True | ||
) | ||
inspect_json = json_loads(inspect_jsonstr) | ||
layers = [ | ||
layer["MIMEType"].endswith("tar+gzip+encrypted") | ||
for layer in inspect_json["LayersData"] | ||
] | ||
if not all(layers): | ||
print("Some layers in image {} are not encrypted!".format(encrypted_image_tag)) | ||
stop_coco_keyprovider() | ||
raise RuntimeError("Image encryption failed!") | ||
|
||
# Create a secret in KBS with the encryption key. Skopeo needs it as raw | ||
# bytes, whereas KBS wants it base64 encoded, so we do the conversion first | ||
with open(SKOPEO_ENCRYPTION_KEY, "rb") as fh: | ||
key_b64 = b64encode(fh.read()).decode() | ||
|
||
create_kbs_secret(encryption_key_resource_id, key_b64) | ||
|
||
if sign: | ||
sign_container_image(encrypted_image_tag) | ||
|
||
stop_coco_keyprovider() |
Oops, something went wrong.