Files
cocos/agent/algorithm/docker/docker.go
T
Sammy Kerata Oina c1cbcec851
CI / lint (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled
COCOS-577 - Introduce Go-based CoRIM generation and deprecate Rust attestation policy scripts. (#578)
* feat: Introduce Go-based CoRIM generation and deprecate Rust attestation policy scripts.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Update dependencies and refactor attestation policy handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Migrate attestation verification to use CoRIM and remove deprecated policy handling and EAT verification tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Removed the `tdx` and `sev-snp` attestation policy scripts and their build configurations, along with related build and installation steps from the main Makefile.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: Remove Rust CI workflow and Cargo Dependabot configuration, and enhance Go test setup for attestation policy paths.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Use WriteString instead of Write([]byte) for writing policy file content in test.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Refactor `ca-bundle` command to fetch bundles by product string using a configurable HTTP getter with improved error handling, and simplify `attestation_policy` command usage.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: ignore return value of cmd.Help()

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Implement CoRIM generation for Azure and GCP attestation policies and add a CLI command to download and verify GCP OVMF files.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Upgrade Python virtual environment setup to include setuptools and wheel, append computation ID to Docker container names, and improve test robustness with error assertions and conditional skips for runtime tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: Enhance attestation verification tests, including CoRIM integration and specific platform types like Azure SNP, vTPM, TDX, and IGVM.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add comprehensive test cases for `VerifyWithCoRIM` including success and measurement mismatch, and refine reference value validation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add Azure and TDX attestation verification tests and abstract external service dependencies for improved testability.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add new test cases for Azure measurement extraction, EAT platform types, IGVM measurement stopping, vTPM CoRIM verification, and GCP OVMF download CLI.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: enhance CLI CoRIM generation and ATLS certificate verification tests, and refactor the Azure MAA client to use an interface.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-03-19 17:01:24 +01:00

185 lines
4.8 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package docker
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"os"
"path"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/ultravioletrs/cocos/agent/algorithm"
"github.com/ultravioletrs/cocos/agent/algorithm/logging"
"github.com/ultravioletrs/cocos/agent/events"
)
const (
containerName = "agent_container"
datasetsMountPath = "/cocos/datasets"
resultsMountPath = "/cocos/results"
)
var _ algorithm.Algorithm = (*docker)(nil)
type docker struct {
algoFile string
logger *slog.Logger
stderr io.Writer
stdout io.Writer
cmpID string
}
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile, cmpID string) algorithm.Algorithm {
d := &docker{
algoFile: algoFile,
logger: logger,
stderr: &logging.Stderr{Logger: logger, EventSvc: eventsSvc, CmpID: cmpID},
stdout: &logging.Stdout{Logger: logger},
cmpID: cmpID,
}
return d
}
func (d *docker) Run() error {
// Create a new Docker client.
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("could not create a new Docker client: %v", err)
}
// Open the Docker image tar file.
imageFile, err := os.Open(d.algoFile)
if err != nil {
return fmt.Errorf("could not open Docker image: %v", err)
}
defer imageFile.Close()
ctx := context.Background()
// Load the Docker image from the tar file.
resp, err := cli.ImageLoad(ctx, imageFile, client.ImageLoadWithQuiet(true))
if err != nil {
return fmt.Errorf("could not load Docker image from file: %v", err)
}
defer resp.Body.Close()
// List the loaded images to get the image ID.
images, err := cli.ImageList(ctx, image.ListOptions{})
if err != nil {
return fmt.Errorf("could not get the Docker image list: %v", err)
}
var imageID string = ""
var dockerImageName string = ""
for _, image := range images {
for _, tag := range image.RepoTags {
imageID = image.ID
dockerImageName = tag
break
}
}
if imageID == "" {
return fmt.Errorf("could not find image ID")
}
// Create and start the container.
respContainer, err := cli.ContainerCreate(ctx, &container.Config{
Image: dockerImageName,
Tty: true,
AttachStdout: true,
AttachStderr: true,
}, &container.HostConfig{
Mounts: []mount.Mount{
{
Type: mount.TypeBind,
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.DatasetsDir),
Target: datasetsMountPath,
},
{
Type: mount.TypeBind,
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.ResultsDir),
Target: resultsMountPath,
},
},
}, nil, nil, fmt.Sprintf("%s-%s", containerName, d.cmpID))
if err != nil {
return fmt.Errorf("could not create a Docker container: %v", err)
}
if err := cli.ContainerStart(ctx, respContainer.ID, container.StartOptions{}); err != nil {
return fmt.Errorf("could not start a Docker container: %v", err)
}
stdout, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStdout: true, Follow: true})
if err != nil {
return fmt.Errorf("could not read stdout from the container: %v", err)
}
defer stdout.Close()
go func() {
if err := writeToOut(stdout, d.stdout); err != nil {
d.logger.Warn(fmt.Sprintf("could not write to stdout: %v", err))
}
}()
stderr, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStderr: true, Follow: true})
if err != nil {
d.logger.Warn(fmt.Sprintf("could not read stderr from the container: %v", err))
}
defer stderr.Close()
go func() {
if err := writeToOut(stderr, d.stderr); err != nil {
d.logger.Warn(fmt.Sprintf("could not write to stderr: %v", err))
}
}()
statusCh, errCh := cli.ContainerWait(ctx, respContainer.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
return fmt.Errorf("could not wait for a Docker container: %v", err)
}
case <-statusCh:
}
defer func() {
if err = cli.ContainerRemove(ctx, respContainer.ID, container.RemoveOptions{Force: true}); err != nil {
d.logger.Warn(fmt.Sprintf("error could not remove container: %v", err))
}
if _, err := cli.ImageRemove(ctx, imageID, image.RemoveOptions{Force: true}); err != nil {
d.logger.Warn(fmt.Sprintf("error could not remove image: %v", err))
}
}()
return nil
}
func writeToOut(readCloser io.ReadCloser, ioWriter io.Writer) error {
scanner := bufio.NewScanner(readCloser)
for scanner.Scan() {
if _, err := ioWriter.Write(scanner.Bytes()); err != nil {
return fmt.Errorf("error writing to output: %v", err)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading container logs error: %v", err)
}
return nil
}
func (d *docker) Stop() error {
// To be supported later.
return nil
}