Co-authored-by: Takuro Sato <takurosato@microsoft.com--username>
This commit is contained in:
Takuro Sato 2023-01-24 16:20:40 +00:00 коммит произвёл GitHub
Родитель 0ca3aee7c1
Коммит b23beabf37
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 127 добавлений и 58 удалений

Просмотреть файл

@ -1,2 +1,2 @@
<>??<><><><>
<>
<><>

Просмотреть файл

@ -23,7 +23,7 @@ jobs:
- script: |
set -ex
cd attestation-container
docker build -t attestation-container .
docker build -t attestation-container --build-arg build_test=True .
az acr login --name attestationcontainerregistry
login_server=$(az acr show --name attestationcontainerregistry --query loginServer --output tsv)
image_version="ci-$(echo $(Build.BuildNumber) | sed 's/\.//g')"
@ -38,7 +38,7 @@ jobs:
python3.8 -m venv ./scripts/azure_deployment/.env
source ./scripts/azure_deployment/.env/bin/activate
pip install -r ./scripts/azure_deployment/requirements.txt
python3.8 scripts/azure_deployment/arm_template.py deploy aci --subscription-id $(CCF_AZURE_SUBSCRIPTION_ID) --resource-group attestationContainer --aci-type dynamic-agent --deployment-name ci-$(Build.BuildNumber) --attestation-container-e2e --aci-file-share-name acshare --aci-file-share-account-name attestcontainercistorage --aci-storage-account-key $(ATTESTATION_CONTAINER_AZURE_STORAGE_KEY) > ~/aci_ips
python3.8 scripts/azure_deployment/arm_template.py deploy aci --subscription-id $(CCF_AZURE_SUBSCRIPTION_ID) --resource-group attestationContainer --aci-type dynamic-agent --deployment-name ci-$(Build.BuildNumber) --attestation-container-e2e --ports 22 2522 --aci-file-share-name acshare --aci-file-share-account-name attestcontainercistorage --aci-storage-account-key $(ATTESTATION_CONTAINER_AZURE_STORAGE_KEY) > ~/aci_ips
# Escape newlines: https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash
escape_data() {
local data=$1
@ -76,18 +76,6 @@ jobs:
parameters:
ssh_key: $(sshKey)
- script: |
set -ex
BUILD_NUMBER=$(echo $(Build.BuildNumber) | sed 's/\.//g')
IP_ADDR=$(echo "$(IpAddresses)" | awk -v container_group="ci-$BUILD_NUMBER-business-logic-0" '$1 == container_group {print $2}')
ssh agent@$IP_ADDR -o "StrictHostKeyChecking no" '
workspace="/acci/workspace_$(Build.BuildNumber)"
sudo chmod 600 -R $workspace
sudo rm -rf $workspace
'
name: cleanup_workspace
displayName: "Cleanup Workspace"
- script: |
set -ex
python3.8 -m venv ./scripts/azure_deployment/.env

Просмотреть файл

@ -43,32 +43,27 @@ jobs:
echo -e "Testing connection with $IP_ADDR"
ssh agent@$IP_ADDR -o "StrictHostKeyChecking no" '
echo "Connected successfully for port 22"
workspace="/acci/workspace_$(Build.BuildNumber)"
export GOPATH="$workspace/go" && mkdir -p $GOPATH
export GOTMPDIR="$workspace/tmp" && mkdir -p $GOTMPDIR
cd /usr/src/app/attest
/usr/local/go/bin/go test
# /usr/local/go/bin/go test
./attest.test -test.v
'
name: run_unit_test
displayName: "Unit Test in Attestation Container Instance Deployed ACIs"
- task: GoTool@0
inputs:
version: "1.19.3"
- template: azure_cli.yml
parameters:
app_id: $(ATTESTATION_CONTAINER_CI_APP_ID)
service_principal_password: $(ATTESTATION_CONTAINER_CI_SERVICE_PRINCIPAL_PASSWORD)
tenant: $(ATTESTATION_CONTAINER_CI_TENANT)
displayName: "Unit Test in Attestation Container Instance Deployed to ACIs"
- script: |
set -ex
az account set --subscription $(CCF_AZURE_SUBSCRIPTION_ID)
echo "Testing attestation container..."
cd attestation-container
echo "testing against ${IP_ADDR} ..."
go test . --addr $IP_ADDR:50051 -v
az container logs --resource-group attestationContainer --name ci-$(BUILD_NUMBER)-business-logic-0 --container-name ci-$(BUILD_NUMBER)-attestation-container
echo -e "Running E2E test with $IP_ADDR"
ssh -p 2522 agent@$IP_ADDR -o "StrictHostKeyChecking no" '
echo "Connected successfully for port 2522"
# workspace="/acci/workspace_$(Build.BuildNumber)/dummy-business-logic-container"
# export GOPATH="$workspace/go" && mkdir -p $GOPATH
# export GOTMPDIR="$workspace/tmp" && mkdir -p $GOTMPDIR
cd /usr/src/app
# /usr/local/go/bin/go test -v
deployment_name="ci-$(echo $(Build.BuildNumber) | sed 's/\.//g')"
echo $deployment_name
socket_addr="/mnt/uds/sock"
sudo ./attestation-container.test -addr $socket_addr -test.v
'
name: test_attestation_container
displayName: "Test attestation container"
displayName: "Test attestation container with dummy business logic container Instance Deployed to ACIs"

2
attestation-container/.gitignore поставляемый
Просмотреть файл

@ -1 +1,3 @@
attestation-container
attestation-container.test
attest/attest.test

Просмотреть файл

@ -2,9 +2,10 @@
# To run this:
# docker build -t attestation-container .
# docker run -it --rm -p 50051:50051 attestation-container
# docker run -it --rm attestation-container
FROM golang:1.19
ARG build_test=False
WORKDIR /usr/src/app
@ -13,6 +14,10 @@ COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
# Pre-compile tests to avoid I/O errors in ACI
RUN if [ "$build_test" = "True" ] ; then go test -c && cd attest && go test -c && cd .. ; fi
RUN go build -v -o /usr/local/bin/app .
CMD ["app"]

Просмотреть файл

@ -8,6 +8,7 @@ import (
"log"
"net"
"os"
"path/filepath"
"microsoft/attestation-container/attest"
pb "microsoft/attestation-container/protobuf"
@ -18,7 +19,7 @@ import (
)
var (
port = flag.Int("port", 50051, "The server port")
socketAddress = flag.String("socket-address", "/tmp/attestation-container.sock", "The socket address of Unix domain socket (UDS)")
endorsementServer = flag.String("endorsement-server", "Azure", "Server to fetch attestation endorsement. Value is either 'Azure' or 'AMD'")
)
@ -57,7 +58,7 @@ func main() {
fmt.Println("Attestation container started.")
if _, err := os.Stat(attest.SNP_DEVICE_PATH); err == nil {
fmt.Printf("%s is detected", attest.SNP_DEVICE_PATH)
fmt.Printf("%s is detected\n", attest.SNP_DEVICE_PATH)
} else if errors.Is(err, os.ErrNotExist) {
log.Fatalf("%s is not detected", attest.SNP_DEVICE_PATH)
} else {
@ -67,7 +68,21 @@ func main() {
flag.Parse()
validateFlags()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
// Cleanup
if _, err := os.Stat(*socketAddress); err == nil {
if err := os.RemoveAll(*socketAddress); err != nil {
log.Fatalf("Failed to clean up socket: %s", err)
}
}
// Create parent directory for socketAddress
socketDir := filepath.Dir(*socketAddress)
// os.MkdirAll doesn't return error when the directory already exists
if err := os.MkdirAll(socketDir, os.ModePerm); err != nil {
log.Fatalf("Failed to create directory for Unix domain socket: %s", err)
}
lis, err := net.Listen("unix", *socketAddress)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

Просмотреть файл

@ -5,17 +5,17 @@ import (
"encoding/hex"
"flag"
"log"
"net"
"testing"
"time"
pb "microsoft/attestation-container/protobuf"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
addr = flag.String("addr", "/tmp/attestation-container.sock", "the Unix domain socket address to connect to")
)
const TIMEOUT_IN_SEC = 10
@ -23,7 +23,10 @@ const TIMEOUT_IN_SEC = 10
func TestFetchReport(t *testing.T) {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
dialer := func(addr string, t time.Duration) (net.Conn, error) {
return net.Dial("unix", addr)
}
conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDialer(dialer))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
@ -46,7 +49,10 @@ func TestFetchReport(t *testing.T) {
func TestInputError(t *testing.T) {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
dialer := func(addr string, t time.Duration) (net.Conn, error) {
return net.Dial("unix", addr)
}
conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDialer(dialer))
if err != nil {
log.Fatalf("did not connect: %v", err)
}

Просмотреть файл

@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache 2.0 License.
import json
import os
import subprocess
import time
@ -30,11 +31,12 @@ def get_pubkey():
STARTUP_COMMANDS = {
"dynamic-agent": lambda args: [
"dynamic-agent": lambda args, ssh_port=22: [
"apt-get update",
"apt-get install -y openssh-server rsync sudo",
"sed -i 's/PubkeyAuthentication no/PubkeyAuthentication yes/g' /etc/ssh/sshd_config",
"sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config",
f"sed -i 's/#\s*Port 22/Port {ssh_port}/g' /etc/ssh/sshd_config",
"useradd -m agent",
'echo "agent ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers',
"service ssh restart",
@ -81,10 +83,6 @@ scratch_mount := {"allowed": true}
scratch_unmount := {"allowed": true}
"""
# NOTE: It currently exposes the attestation-container's port (50051) to outside of the container group for e2e testing purpose,
# but it shouldn't be done in actual CCF application.
ATTESTATION_CONTAINER_PORT = 50051
def make_dev_container_command(args):
return [
@ -98,7 +96,22 @@ def make_attestation_container_command(args):
return [
"/bin/sh",
"-c",
" && ".join([*STARTUP_COMMANDS["dynamic-agent"](args), "app"]),
" && ".join(
[
*STARTUP_COMMANDS["dynamic-agent"](args),
f"app -socket-address /mnt/uds/sock",
]
),
]
def make_dummy_business_logic_container_command(args, ssh_port):
return [
"/bin/sh",
"-c",
" && ".join(
[*STARTUP_COMMANDS["dynamic-agent"](args, ssh_port), "tail -f /dev/null"]
),
]
@ -128,12 +141,32 @@ def make_attestation_container(name, image, command, ports, with_volume):
"command": command,
"ports": [{"protocol": "TCP", "port": p} for p in ports],
"environmentVariables": [],
"resources": {"requests": {"memoryInGB": 16, "cpu": 4}},
"resources": {"requests": {"memoryInGB": 8, "cpu": 2}},
},
}
if with_volume:
t["properties"]["volumeMounts"] = [
{"name": "ccfcivolume", "mountPath": "/acci"}
{"name": "ccfcivolume", "mountPath": "/acci"},
{"name": "udsemptydir", "mountPath": "/mnt/uds"},
]
return t
def make_dummy_business_logic_container(name, image, command, ports, with_volume):
t = {
"name": name,
"properties": {
"image": image,
"command": command,
"ports": [{"protocol": "TCP", "port": p} for p in ports],
"environmentVariables": [],
"resources": {"requests": {"memoryInGB": 8, "cpu": 2}},
},
}
if with_volume:
t["properties"]["volumeMounts"] = [
{"name": "ccfcivolume", "mountPath": "/acci"},
{"name": "udsemptydir", "mountPath": "/mnt/uds"},
]
return t
@ -174,7 +207,9 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
parser.add_argument(
"--ports",
help="List of TCP ports to expose publicly on each container",
action="append",
action="extend",
nargs="*",
type=int,
default=[22],
)
@ -219,6 +254,11 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
)
args = parser.parse_args()
if len(args.ports) > 1:
# Remove default value when ports are explicitly specified.
# For example parser.parse_args() returns [22, 22, 2252] for '--ports 22 2252'.
# This if block removes the first 22 because this behavior is not intuitive.
args.ports = args.ports[1:]
# Note: Using ARM templates rather than Python SDK as ConfidentialComputeProperties does not work yet
# with Python SDK (it should but isolationType cannot be specified - bug has been reported!)
@ -244,16 +284,33 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
)
]
else:
# Attestation container E2E test requires two ports as `args.ports`: [<ssh for attestation container>, <ssh for dummy business logic container>]
container_image = f"attestationcontainerregistry.azurecr.io/attestation-container:{args.deployment_name}"
deployment_name = f"{args.deployment_name}-business-logic"
container_name = f"{args.deployment_name}-attestation-container"
command = make_attestation_container_command(args)
args.ports.append(ATTESTATION_CONTAINER_PORT)
container_name_dummy_blc = (
f"{args.deployment_name}-dummy-business-logic-container"
)
command_dummy_blc = make_dummy_business_logic_container_command(
args, args.ports[1]
)
with_volume = args.aci_file_share_name is not None
containers = [
make_attestation_container(
container_name, container_image, command, args.ports, with_volume
)
container_name,
container_image,
command,
args.ports[:1],
with_volume,
),
make_dummy_business_logic_container(
container_name_dummy_blc,
container_image,
command_dummy_blc,
args.ports[1:],
with_volume,
),
]
container_group_properties = {
@ -277,7 +334,8 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
"storageAccountName": args.aci_file_share_account_name,
"storageAccountKey": args.aci_storage_account_key,
},
}
},
{"name": "udsemptydir", "emptyDir": {}},
]
if not args.non_confidential: