mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-23 05:41:19 -05:00
image: add go code to upload image info and measurements
This commit is contained in:
parent
b8751f35f9
commit
217a744606
@ -9,6 +9,11 @@ go_library(
|
|||||||
"flags.go",
|
"flags.go",
|
||||||
"gcp.go",
|
"gcp.go",
|
||||||
"image.go",
|
"image.go",
|
||||||
|
"info.go",
|
||||||
|
"measurements.go",
|
||||||
|
"measurementsenvelope.go",
|
||||||
|
"measurementsmerge.go",
|
||||||
|
"measurementsupload.go",
|
||||||
"must.go",
|
"must.go",
|
||||||
"nop.go",
|
"nop.go",
|
||||||
"openstack.go",
|
"openstack.go",
|
||||||
@ -19,6 +24,7 @@ go_library(
|
|||||||
importpath = "github.com/edgelesssys/constellation/v2/image/upload/internal/cmd",
|
importpath = "github.com/edgelesssys/constellation/v2/image/upload/internal/cmd",
|
||||||
visibility = ["//image/upload:__subpackages__"],
|
visibility = ["//image/upload:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//internal/attestation/measurements",
|
||||||
"//internal/cloud/cloudprovider",
|
"//internal/cloud/cloudprovider",
|
||||||
"//internal/logger",
|
"//internal/logger",
|
||||||
"//internal/osimage",
|
"//internal/osimage",
|
||||||
@ -26,6 +32,8 @@ go_library(
|
|||||||
"//internal/osimage/aws",
|
"//internal/osimage/aws",
|
||||||
"//internal/osimage/azure",
|
"//internal/osimage/azure",
|
||||||
"//internal/osimage/gcp",
|
"//internal/osimage/gcp",
|
||||||
|
"//internal/osimage/imageinfo",
|
||||||
|
"//internal/osimage/measurementsuploader",
|
||||||
"//internal/osimage/nop",
|
"//internal/osimage/nop",
|
||||||
"//internal/osimage/secureboot",
|
"//internal/osimage/secureboot",
|
||||||
"//internal/versionsapi",
|
"//internal/versionsapi",
|
||||||
|
@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@ -198,3 +199,143 @@ func parseGCPFlags(cmd *cobra.Command) (gcpFlags, error) {
|
|||||||
gcpBucket: gcpBucket,
|
gcpBucket: gcpBucket,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type s3Flags struct {
|
||||||
|
region string
|
||||||
|
bucket string
|
||||||
|
logLevel zapcore.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseS3Flags(cmd *cobra.Command) (s3Flags, error) {
|
||||||
|
region, err := cmd.Flags().GetString("region")
|
||||||
|
if err != nil {
|
||||||
|
return s3Flags{}, err
|
||||||
|
}
|
||||||
|
bucket, err := cmd.Flags().GetString("bucket")
|
||||||
|
if err != nil {
|
||||||
|
return s3Flags{}, err
|
||||||
|
}
|
||||||
|
verbose, err := cmd.Flags().GetBool("verbose")
|
||||||
|
if err != nil {
|
||||||
|
return s3Flags{}, err
|
||||||
|
}
|
||||||
|
logLevel := zapcore.InfoLevel
|
||||||
|
if verbose {
|
||||||
|
logLevel = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
return s3Flags{
|
||||||
|
region: region,
|
||||||
|
bucket: bucket,
|
||||||
|
logLevel: logLevel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type measurementsFlags struct {
|
||||||
|
s3Flags
|
||||||
|
measurementsPath string
|
||||||
|
signaturePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUploadMeasurementsFlags(cmd *cobra.Command) (measurementsFlags, error) {
|
||||||
|
s3, err := parseS3Flags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return measurementsFlags{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
measurementsPath, err := cmd.Flags().GetString("measurements")
|
||||||
|
if err != nil {
|
||||||
|
return measurementsFlags{}, err
|
||||||
|
}
|
||||||
|
signaturePath, err := cmd.Flags().GetString("signature")
|
||||||
|
if err != nil {
|
||||||
|
return measurementsFlags{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return measurementsFlags{
|
||||||
|
s3Flags: s3,
|
||||||
|
measurementsPath: measurementsPath,
|
||||||
|
signaturePath: signaturePath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeMeasurementsFlags struct {
|
||||||
|
out string
|
||||||
|
logLevel zapcore.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMergeMeasurementsFlags(cmd *cobra.Command) (mergeMeasurementsFlags, error) {
|
||||||
|
out, err := cmd.Flags().GetString("out")
|
||||||
|
if err != nil {
|
||||||
|
return mergeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
verbose, err := cmd.Flags().GetBool("verbose")
|
||||||
|
if err != nil {
|
||||||
|
return mergeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
logLevel := zapcore.InfoLevel
|
||||||
|
if verbose {
|
||||||
|
logLevel = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeMeasurementsFlags{
|
||||||
|
out: out,
|
||||||
|
logLevel: logLevel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type envelopeMeasurementsFlags struct {
|
||||||
|
version versionsapi.Version
|
||||||
|
csp cloudprovider.Provider
|
||||||
|
attestationVariant string
|
||||||
|
in, out string
|
||||||
|
logLevel zapcore.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEnvelopeMeasurementsFlags(cmd *cobra.Command) (envelopeMeasurementsFlags, error) {
|
||||||
|
version, err := cmd.Flags().GetString("version")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
ver, err := versionsapi.NewVersionFromShortPath(version, versionsapi.VersionKindImage)
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
csp, err := cmd.Flags().GetString("csp")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
provider := cloudprovider.FromString(csp)
|
||||||
|
attestationVariant, err := cmd.Flags().GetString("attestation-variant")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
if provider == cloudprovider.Unknown {
|
||||||
|
return envelopeMeasurementsFlags{}, errors.New("unknown cloud provider")
|
||||||
|
}
|
||||||
|
in, err := cmd.Flags().GetString("in")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
out, err := cmd.Flags().GetString("out")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
verbose, err := cmd.Flags().GetBool("verbose")
|
||||||
|
if err != nil {
|
||||||
|
return envelopeMeasurementsFlags{}, err
|
||||||
|
}
|
||||||
|
logLevel := zapcore.InfoLevel
|
||||||
|
if verbose {
|
||||||
|
logLevel = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
return envelopeMeasurementsFlags{
|
||||||
|
version: ver,
|
||||||
|
csp: provider,
|
||||||
|
attestationVariant: attestationVariant,
|
||||||
|
in: in,
|
||||||
|
out: out,
|
||||||
|
logLevel: logLevel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
83
image/upload/internal/cmd/info.go
Normal file
83
image/upload/internal/cmd/info.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
infoupload "github.com/edgelesssys/constellation/v2/internal/osimage/imageinfo"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInfoCmd creates a new info parent command.
|
||||||
|
func NewInfoCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "info [flags] <image-info.json>...",
|
||||||
|
Short: "Uploads OS image info to S3",
|
||||||
|
Long: "Uploads OS image info to S3.",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: runInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
|
cmd.Flags().String("region", "eu-central-1", "AWS region of the archive S3 bucket")
|
||||||
|
cmd.Flags().String("bucket", "cdn-constellation-backend", "S3 bucket name of the archive")
|
||||||
|
cmd.Flags().Bool("verbose", false, "Enable verbose output")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInfo(cmd *cobra.Command, args []string) error {
|
||||||
|
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
|
||||||
|
if len(workdir) > 0 {
|
||||||
|
must(os.Chdir(workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := parseS3Flags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.New(logger.PlainLog, flags.logLevel)
|
||||||
|
log.Debugf("Parsed flags: %+v", flags)
|
||||||
|
info, err := readInfoArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadC, err := infoupload.New(cmd.Context(), flags.region, flags.bucket, log)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := uploadC.Upload(cmd.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image info: %w", err)
|
||||||
|
}
|
||||||
|
log.Infof("Uploaded image info to %s", url)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInfoArgs(paths []string) (versionsapi.ImageInfo, error) {
|
||||||
|
infos := make([]versionsapi.ImageInfo, len(paths))
|
||||||
|
for i, path := range paths {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return versionsapi.ImageInfo{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := json.NewDecoder(f).Decode(&infos[i]); err != nil {
|
||||||
|
return versionsapi.ImageInfo{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return versionsapi.MergeImageInfos(infos...)
|
||||||
|
}
|
32
image/upload/internal/cmd/measurements.go
Normal file
32
image/upload/internal/cmd/measurements.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMeasurementsCmd creates a new measurements command. Measurements needs another
|
||||||
|
// verb, and does nothing on its own.
|
||||||
|
func NewMeasurementsCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "measurements",
|
||||||
|
Short: "Handle OS image measurements",
|
||||||
|
Long: "Handle OS image measurements.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
|
cmd.AddCommand(newMeasurementsUploadCmd())
|
||||||
|
cmd.AddCommand(newMeasurementsMergeCmd())
|
||||||
|
cmd.AddCommand(newMeasurementsEnvelopeCmd())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
101
image/upload/internal/cmd/measurementsenvelope.go
Normal file
101
image/upload/internal/cmd/measurementsenvelope.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newMeasurementsEnvelopeCmd creates a new envelope command.
|
||||||
|
func newMeasurementsEnvelopeCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "envelope",
|
||||||
|
Short: "Envelope OS image measurements",
|
||||||
|
Long: "Envelope OS image measurements for one variant to follow the measurements v2 format.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runEnvelopeMeasurements,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
cmd.Flags().String("version", "", "Shortname of the os image version.")
|
||||||
|
cmd.Flags().String("csp", "", "CSP of this image measurement.")
|
||||||
|
cmd.Flags().String("attestation-variant", "", "Attestation variant of the image measurements.")
|
||||||
|
cmd.Flags().String("in", "", "Path to read the raw measurements from.")
|
||||||
|
cmd.Flags().String("out", "", "Optional path to write the enveloped result to. If not set, the result is written to stdout.")
|
||||||
|
cmd.Flags().Bool("verbose", false, "Enable verbose output")
|
||||||
|
|
||||||
|
must(cmd.MarkFlagRequired("version"))
|
||||||
|
must(cmd.MarkFlagRequired("csp"))
|
||||||
|
must(cmd.MarkFlagRequired("attestation-variant"))
|
||||||
|
must(cmd.MarkFlagRequired("in"))
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEnvelopeMeasurements(cmd *cobra.Command, _ []string) error {
|
||||||
|
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
|
||||||
|
if len(workdir) > 0 {
|
||||||
|
must(os.Chdir(workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := parseEnvelopeMeasurementsFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.New(logger.PlainLog, flags.logLevel)
|
||||||
|
log.Debugf("Parsed flags: %+v", flags)
|
||||||
|
|
||||||
|
f, err := os.Open(flags.in)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("enveloping measurements: opening input file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
var measuremnt rawMeasurements
|
||||||
|
if err := json.NewDecoder(f).Decode(&measuremnt); err != nil {
|
||||||
|
return fmt.Errorf("enveloping measurements: reading input file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enveloped := measurements.ImageMeasurementsV2{
|
||||||
|
Ref: flags.version.Ref,
|
||||||
|
Stream: flags.version.Stream,
|
||||||
|
Version: flags.version.Version,
|
||||||
|
List: []measurements.ImageMeasurementsV2Entry{
|
||||||
|
{
|
||||||
|
CSP: flags.csp,
|
||||||
|
AttestationVariant: flags.attestationVariant,
|
||||||
|
Measurements: measuremnt.Measurements,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := cmd.OutOrStdout()
|
||||||
|
if len(flags.out) > 0 {
|
||||||
|
outF, err := os.Create(flags.out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("enveloping measurements: opening output file: %w", err)
|
||||||
|
}
|
||||||
|
defer outF.Close()
|
||||||
|
out = outF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(out).Encode(enveloped); err != nil {
|
||||||
|
return fmt.Errorf("enveloping measurements: writing output file: %w", err)
|
||||||
|
}
|
||||||
|
log.Infof("Enveloped image measurements")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawMeasurements struct {
|
||||||
|
Measurements measurements.M `json:"measurements"`
|
||||||
|
}
|
85
image/upload/internal/cmd/measurementsmerge.go
Normal file
85
image/upload/internal/cmd/measurementsmerge.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newMeasurementsMergeCmd creates a new merge command.
|
||||||
|
func newMeasurementsMergeCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "merge [flags] <measurements.json>...",
|
||||||
|
Short: "Merge OS image measurements",
|
||||||
|
Long: "Merge OS image measurements.",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: runMergeMeasurements,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
cmd.Flags().String("out", "", "Optional path to write the merge result to. If not set, the result is written to stdout.")
|
||||||
|
cmd.Flags().Bool("verbose", false, "Enable verbose output")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMergeMeasurements(cmd *cobra.Command, args []string) error {
|
||||||
|
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
|
||||||
|
if len(workdir) > 0 {
|
||||||
|
must(os.Chdir(workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := parseMergeMeasurementsFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.New(logger.PlainLog, flags.logLevel)
|
||||||
|
log.Debugf("Parsed flags: %+v", flags)
|
||||||
|
|
||||||
|
mergedMeasurements, err := readMeasurementsArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("merging measurements: reading input files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := cmd.OutOrStdout()
|
||||||
|
if len(flags.out) > 0 {
|
||||||
|
outF, err := os.Create(flags.out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("merging measurements: opening output file: %w", err)
|
||||||
|
}
|
||||||
|
defer outF.Close()
|
||||||
|
out = outF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(out).Encode(mergedMeasurements); err != nil {
|
||||||
|
return fmt.Errorf("merging measurements: writing output file: %w", err)
|
||||||
|
}
|
||||||
|
log.Infof("Merged image measurements")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMeasurementsArgs(paths []string) (measurements.ImageMeasurementsV2, error) {
|
||||||
|
measuremnts := make([]measurements.ImageMeasurementsV2, len(paths))
|
||||||
|
for i, path := range paths {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return measurements.ImageMeasurementsV2{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := json.NewDecoder(f).Decode(&measuremnts[i]); err != nil {
|
||||||
|
return measurements.ImageMeasurementsV2{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return measurements.MergeImageMeasurementsV2(measuremnts...)
|
||||||
|
}
|
78
image/upload/internal/cmd/measurementsupload.go
Normal file
78
image/upload/internal/cmd/measurementsupload.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/osimage/measurementsuploader"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newMeasurementsUploadCmd creates a new upload command.
|
||||||
|
func newMeasurementsUploadCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "upload",
|
||||||
|
Short: "Uploads OS image measurements to S3",
|
||||||
|
Long: "Uploads OS image measurements to S3.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runMeasurementsUpload,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
|
cmd.Flags().String("measurements", "", "Path to measurements file to upload")
|
||||||
|
cmd.Flags().String("signature", "", "Path to signature file to upload")
|
||||||
|
cmd.Flags().String("region", "eu-central-1", "AWS region of the archive S3 bucket")
|
||||||
|
cmd.Flags().String("bucket", "cdn-constellation-backend", "S3 bucket name of the archive")
|
||||||
|
cmd.Flags().Bool("verbose", false, "Enable verbose output")
|
||||||
|
|
||||||
|
must(cmd.MarkFlagRequired("measurements"))
|
||||||
|
must(cmd.MarkFlagRequired("signature"))
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMeasurementsUpload(cmd *cobra.Command, _ []string) error {
|
||||||
|
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
|
||||||
|
if len(workdir) > 0 {
|
||||||
|
must(os.Chdir(workdir))
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := parseUploadMeasurementsFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.New(logger.PlainLog, flags.logLevel)
|
||||||
|
log.Debugf("Parsed flags: %+v", flags)
|
||||||
|
|
||||||
|
uploadC, err := measurementsuploader.New(cmd.Context(), flags.region, flags.bucket, log)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
measurements, err := os.Open(flags.measurementsPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image measurements: opening measurements file: %w", err)
|
||||||
|
}
|
||||||
|
defer measurements.Close()
|
||||||
|
signature, err := os.Open(flags.signaturePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image measurements: opening signature file: %w", err)
|
||||||
|
}
|
||||||
|
defer signature.Close()
|
||||||
|
|
||||||
|
measurementsURL, signatureURL, err := uploadC.Upload(cmd.Context(), measurements, signature)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("uploading image info: %w", err)
|
||||||
|
}
|
||||||
|
log.Infof("Uploaded image measurements to %s (and signature to %s)", measurementsURL, signatureURL)
|
||||||
|
return nil
|
||||||
|
}
|
@ -41,6 +41,8 @@ func newRootCmd() *cobra.Command {
|
|||||||
rootCmd.SetOut(os.Stdout)
|
rootCmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
rootCmd.AddCommand(cmd.NewImageCmd())
|
rootCmd.AddCommand(cmd.NewImageCmd())
|
||||||
|
rootCmd.AddCommand(cmd.NewInfoCmd())
|
||||||
|
rootCmd.AddCommand(cmd.NewMeasurementsCmd())
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ go_library(
|
|||||||
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/archive",
|
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/archive",
|
||||||
visibility = ["//:__subpackages__"],
|
visibility = ["//:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//internal/constants",
|
||||||
"//internal/logger",
|
"//internal/logger",
|
||||||
"//internal/versionsapi",
|
"//internal/versionsapi",
|
||||||
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
)
|
)
|
||||||
@ -58,11 +59,9 @@ func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, cs
|
|||||||
Body: img,
|
Body: img,
|
||||||
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
||||||
})
|
})
|
||||||
return baseURL + key, err
|
return constants.CDNRepositoryURL + "/" + key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type uploadClient interface {
|
type uploadClient interface {
|
||||||
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
|
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = "https://cdn.confidential.cloud/"
|
|
||||||
|
17
internal/osimage/imageinfo/BUILD.bazel
Normal file
17
internal/osimage/imageinfo/BUILD.bazel
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "imageinfo",
|
||||||
|
srcs = ["imageinfo.go"],
|
||||||
|
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/imageinfo",
|
||||||
|
visibility = ["//:__subpackages__"],
|
||||||
|
deps = [
|
||||||
|
"//internal/constants",
|
||||||
|
"//internal/logger",
|
||||||
|
"//internal/versionsapi",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
|
||||||
|
],
|
||||||
|
)
|
78
internal/osimage/imageinfo/imageinfo.go
Normal file
78
internal/osimage/imageinfo/imageinfo.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
// package imageinfo is used to upload image info JSON files to S3.
|
||||||
|
package imageinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uploader uploads image info to S3.
|
||||||
|
type Uploader struct {
|
||||||
|
uploadClient uploadClient
|
||||||
|
// bucket is the name of the S3 bucket to use.
|
||||||
|
bucket string
|
||||||
|
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Uploader.
|
||||||
|
func New(ctx context.Context, region, bucket string, log *logger.Logger) (*Uploader, error) {
|
||||||
|
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s3client := s3.NewFromConfig(cfg)
|
||||||
|
uploadClient := s3manager.NewUploader(s3client)
|
||||||
|
|
||||||
|
return &Uploader{
|
||||||
|
uploadClient: uploadClient,
|
||||||
|
bucket: bucket,
|
||||||
|
log: log,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload marshals the image info to JSON and uploads it to S3.
|
||||||
|
func (a *Uploader) Upload(ctx context.Context, imageInfo versionsapi.ImageInfo) (string, error) {
|
||||||
|
ver := versionsapi.Version{
|
||||||
|
Ref: imageInfo.Ref,
|
||||||
|
Stream: imageInfo.Stream,
|
||||||
|
Version: imageInfo.Version,
|
||||||
|
Kind: versionsapi.VersionKindImage,
|
||||||
|
}
|
||||||
|
key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "info.json")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
a.log.Debugf("Archiving image info to s3://%v/%v", a.bucket, key)
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if err := json.NewEncoder(buf).Encode(imageInfo); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: &a.bucket,
|
||||||
|
Key: &key,
|
||||||
|
Body: buf,
|
||||||
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
||||||
|
})
|
||||||
|
return constants.CDNRepositoryURL + "/" + key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type uploadClient interface {
|
||||||
|
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
|
||||||
|
}
|
18
internal/osimage/measurementsuploader/BUILD.bazel
Normal file
18
internal/osimage/measurementsuploader/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "measurementsuploader",
|
||||||
|
srcs = ["measurementsuploader.go"],
|
||||||
|
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/measurementsuploader",
|
||||||
|
visibility = ["//:__subpackages__"],
|
||||||
|
deps = [
|
||||||
|
"//internal/attestation/measurements",
|
||||||
|
"//internal/constants",
|
||||||
|
"//internal/logger",
|
||||||
|
"//internal/versionsapi",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
|
||||||
|
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
// package measurementsuploader is used to upload measurements (v2) JSON files (and signatures) to S3.
|
||||||
|
package measurementsuploader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uploader uploads image info to S3.
|
||||||
|
type Uploader struct {
|
||||||
|
uploadClient uploadClient
|
||||||
|
// bucket is the name of the S3 bucket to use.
|
||||||
|
bucket string
|
||||||
|
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Uploader.
|
||||||
|
func New(ctx context.Context, region, bucket string, log *logger.Logger) (*Uploader, error) {
|
||||||
|
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s3client := s3.NewFromConfig(cfg)
|
||||||
|
uploadClient := s3manager.NewUploader(s3client)
|
||||||
|
|
||||||
|
return &Uploader{
|
||||||
|
uploadClient: uploadClient,
|
||||||
|
bucket: bucket,
|
||||||
|
log: log,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload uploads the measurements v2 JSON file and its signature to S3.
|
||||||
|
func (a *Uploader) Upload(ctx context.Context, rawMeasurement, signature io.ReadSeeker) (string, string, error) {
|
||||||
|
// parse the measurements to get the ref, stream, and version
|
||||||
|
var measurements measurements.ImageMeasurementsV2
|
||||||
|
if err := json.NewDecoder(rawMeasurement).Decode(&measurements); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if _, err := rawMeasurement.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ver := versionsapi.Version{
|
||||||
|
Ref: measurements.Ref,
|
||||||
|
Stream: measurements.Stream,
|
||||||
|
Version: measurements.Version,
|
||||||
|
Kind: versionsapi.VersionKindImage,
|
||||||
|
}
|
||||||
|
key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "measurements.json")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
sigKey, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "measurements.json.sig")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
a.log.Debugf("Archiving image measurements to s3://%v/%v and s3://%v/%v", a.bucket, key, a.bucket, sigKey)
|
||||||
|
if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: &a.bucket,
|
||||||
|
Key: &key,
|
||||||
|
Body: rawMeasurement,
|
||||||
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
||||||
|
}); err != nil {
|
||||||
|
return "", "", fmt.Errorf("uploading measurements: %w", err)
|
||||||
|
}
|
||||||
|
if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: &a.bucket,
|
||||||
|
Key: &sigKey,
|
||||||
|
Body: signature,
|
||||||
|
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
|
||||||
|
}); err != nil {
|
||||||
|
return "", "", fmt.Errorf("uploading measurements signature: %w", err)
|
||||||
|
}
|
||||||
|
return constants.CDNRepositoryURL + "/" + key, constants.CDNRepositoryURL + "/" + sigKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uploadClient interface {
|
||||||
|
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user