Skip to content

Commit

Permalink
Add TPM PCR measurement precalculation
Browse files Browse the repository at this point in the history
Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
  • Loading branch information
msanft committed Jun 5, 2024
1 parent 73b6208 commit 3a99b73
Show file tree
Hide file tree
Showing 23 changed files with 1,621 additions and 296 deletions.
297 changes: 1 addition & 296 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,9 @@ SPDX-License-Identifier: Apache-2.0
package main

import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"

"github.com/BurntSushi/toml"
"github.com/edgelesssys/uplosi/aws"
"github.com/edgelesssys/uplosi/azure"
"github.com/edgelesssys/uplosi/config"
"github.com/edgelesssys/uplosi/gcp"
"github.com/edgelesssys/uplosi/openstack"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)

const (
configName = "uplosi.conf"
configDir = "uplosi.conf.d"
)

var version = "0.0.0-dev"
Expand All @@ -44,281 +23,7 @@ func newRootCmd() *cobra.Command {
cmd.SetOut(os.Stdout)
cmd.InitDefaultVersionFlag()
cmd.AddCommand(newUploadCmd())
cmd.AddCommand(newPrecalculateMeasurementsCmd())

return cmd
}

func newUploadCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "upload <image>",
Short: "Upload an image to a cloud provider",
Args: cobra.ExactArgs(1),
RunE: runUpload,
}
cmd.Flags().BoolP("increment-version", "i", false, "increment version number after upload")
cmd.Flags().StringSlice("enable-variant-glob", []string{"*"}, "list of variant name globs to enable")
cmd.Flags().StringSlice("disable-variant-glob", nil, "list of variant name globs to disable")
cmd.Flags().StringP("config", "c", "", fmt.Sprintf("path to directory %s and %s resides in", configName, configDir))

return cmd
}

func runUpload(cmd *cobra.Command, args []string) error {
logger := log.New(cmd.ErrOrStderr(), "", log.LstdFlags)
imagePath := args[0]

flags, err := parseUploadFlags(cmd)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}

conf, err := parseConfigFiles(flags.configPath)
if err != nil {
return fmt.Errorf("parsing config files: %w", err)
}

versionFiles := map[string][]byte{}
versionFileLookup := func(name string) ([]byte, error) {
if _, ok := versionFiles[name]; !ok {
ver, err := os.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("reading version file: %w", err)
}
versionFiles[name] = ver
}
return versionFiles[name], nil
}

allRefs := []string{}
err = conf.ForEach(
func(name string, cfg config.Config) error {
refs, err := uploadVariant(cmd.Context(), imagePath, name, cfg, logger)
if err != nil {
return err
}
allRefs = append(allRefs, refs...)
return nil
},
versionFileLookup,
func(name string) bool {
return filterGlobAny(flags.enableVariantGlobs, name)
},
func(name string) bool {
return !filterGlobAny(flags.disableVariantGlobs, name)
},
)
if err != nil {
return fmt.Errorf("uploading variants: %w", err)
}

for _, ref := range allRefs {
fmt.Println(ref)
}

if !flags.incrementVersion {
return nil
}
if len(versionFiles) == 0 {
return errors.New("increment-version flag set but no version files found")
}
for versionFileName, version := range versionFiles {
newVer, err := incrementSemver(strings.TrimSpace(string(version)))
if err != nil {
return fmt.Errorf("incrementing semver: %w", err)
}
if err := writeVersionFile(versionFileName, []byte(newVer)); err != nil {
return fmt.Errorf("writing version file: %w", err)
}
}
return nil
}

func uploadVariant(ctx context.Context, imagePath, variant string, config config.Config, logger *log.Logger) ([]string, error) {
var prepper Prepper
var upload Uploader
var err error

if len(variant) > 0 {
log.Println("Uploading variant", variant)
}

switch strings.ToLower(config.Provider) {
case "aws":
prepper = &aws.Prepper{}
upload, err = aws.NewUploader(config, logger)
if err != nil {
return nil, fmt.Errorf("creating aws uploader: %w", err)
}
case "azure":
prepper = &azure.Prepper{}
upload, err = azure.NewUploader(config, logger)
if err != nil {
return nil, fmt.Errorf("creating azure uploader: %w", err)
}
case "gcp":
prepper = &gcp.Prepper{}
upload, err = gcp.NewUploader(config, logger)
if err != nil {
return nil, fmt.Errorf("creating gcp uploader: %w", err)
}
case "openstack":
prepper = &openstack.Prepper{}
upload, err = openstack.NewUploader(config, logger)
if err != nil {
return nil, fmt.Errorf("creating openstack uploader: %w", err)
}
default:
return nil, fmt.Errorf("unknown provider: %s", config.Provider)
}

tmpDir, err := os.MkdirTemp("", "uplosi-")
if err != nil {
return nil, fmt.Errorf("creating temp dir: %w", err)
}
defer os.RemoveAll(tmpDir)

imagePath, err = prepper.Prepare(ctx, imagePath, tmpDir)
if err != nil {
return nil, fmt.Errorf("preparing image: %w", err)
}
image, err := os.Open(imagePath)
if err != nil {
return nil, fmt.Errorf("opening image: %w", err)
}
defer image.Close()
imageFi, err := image.Stat()
if err != nil {
return nil, fmt.Errorf("getting image stats: %w", err)
}

refs, err := upload.Upload(ctx, image, imageFi.Size())
if err != nil {
return nil, fmt.Errorf("uploading image: %w", err)
}

return refs, nil
}

type uploadFlags struct {
incrementVersion bool
enableVariantGlobs []string
disableVariantGlobs []string
configPath string
}

func parseUploadFlags(cmd *cobra.Command) (*uploadFlags, error) {
incrementVersion, err := cmd.Flags().GetBool("increment-version")
if err != nil {
return nil, fmt.Errorf("getting increment-version flag: %w", err)
}
enableVariantGlobs, err := cmd.Flags().GetStringSlice("enable-variant-glob")
if err != nil {
return nil, fmt.Errorf("getting enable-variant-glob flag: %w", err)
}
disableVariantGlobs, err := cmd.Flags().GetStringSlice("disable-variant-glob")
if err != nil {
return nil, fmt.Errorf("getting disable-variant-glob flag: %w", err)
}
configPath, err := cmd.Flags().GetString("config")
if err != nil {
return nil, fmt.Errorf("getting config flag: %w", err)
}
return &uploadFlags{
incrementVersion: incrementVersion,
enableVariantGlobs: enableVariantGlobs,
disableVariantGlobs: disableVariantGlobs,
configPath: configPath,
}, nil
}

func filterGlobAny(globs []string, name string) bool {
for _, glob := range globs {
if ok, _ := filepath.Match(glob, name); ok {
return true
}
}
return false
}

func readTOMLFile(path string, data any) error {
configFile, err := os.OpenFile(path, os.O_RDONLY, os.ModeAppend)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer configFile.Close()
if _, err := toml.NewDecoder(configFile).Decode(data); err != nil {
return fmt.Errorf("decoding file: %w", err)
}
return nil
}

func writeVersionFile(path string, data []byte) error {
versionFile, err := os.OpenFile(path, os.O_WRONLY, os.ModeAppend)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer versionFile.Close()
if _, err := versionFile.Write(data); err != nil {
return fmt.Errorf("writing file: %w", err)
}
return nil
}

type Prepper interface {
Prepare(ctx context.Context, imagePath, tmpDir string) (string, error)
}

type Uploader interface {
Upload(ctx context.Context, image io.ReadSeeker, size int64) (refs []string, retErr error)
}

func parseConfigFiles(configPath string) (*config.ConfigFile, error) {
configLocation := path.Join(configPath, configName)
configDirLocation := path.Join(configPath, configDir)

var conf config.ConfigFile
if err := readTOMLFile(configLocation, &conf); err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}

dirEntries, err := os.ReadDir(configDirLocation)
if os.IsNotExist(err) {
return &conf, nil
}
if err != nil {
return nil, fmt.Errorf("reading config dir: %w", err)
}
for _, dirEntry := range dirEntries {
var cfgOverlay config.ConfigFile
if dirEntry.IsDir() {
continue
}
if filepath.Ext(dirEntry.Name()) != ".conf" {
continue
}
if err := readTOMLFile(filepath.Join(configDir, dirEntry.Name()), &cfgOverlay); err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}
if err := conf.Merge(cfgOverlay); err != nil {
return nil, fmt.Errorf("merging config: %w", err)
}
}
return &conf, nil
}

func incrementSemver(version string) (string, error) {
canonical := strings.TrimPrefix(semver.Canonical("v"+version), "v")
parts := strings.Split(canonical, ".")
if len(parts) != 3 {
return "", fmt.Errorf("splitting canonical version: %s, %v", canonical, parts)
}

patch := parts[2]
patchNum, err := strconv.Atoi(patch)
if err != nil {
return "", fmt.Errorf("converting patch number: %w", err)
}

patchNum++
return fmt.Sprintf("%s.%s.%d", parts[0], parts[1], patchNum), nil
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ require (
require (
cloud.google.com/go/auth v0.4.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
github.com/foxboron/go-uefi v0.0.0-20240522180132-205d5597883a // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
)

require (
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxboron/go-uefi v0.0.0-20240522180132-205d5597883a h1:Q/VIO3QAlaF95JqVVF39udInPR76lu02yrMDInavm8Q=
github.com/foxboron/go-uefi v0.0.0-20240522180132-205d5597883a/go.mod h1:ffg/fkDeOYicEQLoO2yFFGt00KUTYVXI+rfnc8il6vQ=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
Expand Down Expand Up @@ -226,6 +228,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand Down Expand Up @@ -268,6 +272,8 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down
Loading

0 comments on commit 3a99b73

Please sign in to comment.