mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-20 04:04:21 -04:00
attestationconfigapi: revise upload frequency (#3238)
* Add attestationconfigapi compare command * Only upload the lowest version for each verify test --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
a6b0885f18
commit
6c24963570
8 changed files with 153 additions and 50 deletions
12
.github/actions/e2e_verify/action.yml
vendored
12
.github/actions/e2e_verify/action.yml
vendored
|
@ -94,13 +94,11 @@ runs:
|
||||||
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
||||||
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
||||||
run: |
|
run: |
|
||||||
reports=(attestation-report-*.json)
|
reports=attestation-report-*.json
|
||||||
if [ -z ${#reports[@]} ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for file in "${reports[@]}"; do
|
report=$(bazel run //internal/api/attestationconfigapi/cli -- compare ${{ inputs.attestationVariant }} ${reports})
|
||||||
path=$(realpath "${file}")
|
|
||||||
|
path=$(realpath "${report}")
|
||||||
cat "${path}"
|
cat "${path}"
|
||||||
|
|
||||||
bazel run //internal/api/attestationconfigapi/cli -- upload ${{ inputs.attestationVariant }} attestation-report "${path}"
|
bazel run //internal/api/attestationconfigapi/cli -- upload ${{ inputs.attestationVariant }} attestation-report "${path}"
|
||||||
done
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ go_binary(
|
||||||
go_library(
|
go_library(
|
||||||
name = "cli_lib",
|
name = "cli_lib",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"compare.go",
|
||||||
"delete.go",
|
"delete.go",
|
||||||
"main.go",
|
"main.go",
|
||||||
"upload.go",
|
"upload.go",
|
||||||
|
|
|
@ -31,6 +31,25 @@ func reportVersionDir(attestation variant.Variant) string {
|
||||||
return path.Join(attestationconfigapi.AttestationURLPath, attestation.String(), cachedVersionsSubDir)
|
return path.Join(attestationconfigapi.AttestationURLPath, attestation.String(), cachedVersionsSubDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInputNewerThanOtherVersion compares the input version with the other version and returns true if the input version is newer.
|
||||||
|
// This function panics if the input versions are not TDX or SEV-SNP versions.
|
||||||
|
func IsInputNewerThanOtherVersion(variant variant.Variant, inputVersion, otherVersion any) bool {
|
||||||
|
var result bool
|
||||||
|
actionForVariant(variant,
|
||||||
|
func() {
|
||||||
|
input := inputVersion.(attestationconfigapi.TDXVersion)
|
||||||
|
other := otherVersion.(attestationconfigapi.TDXVersion)
|
||||||
|
result = isInputNewerThanOtherTDXVersion(input, other)
|
||||||
|
},
|
||||||
|
func() {
|
||||||
|
input := inputVersion.(attestationconfigapi.SEVSNPVersion)
|
||||||
|
other := otherVersion.(attestationconfigapi.SEVSNPVersion)
|
||||||
|
result = isInputNewerThanOtherSEVSNPVersion(input, other)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// UploadLatestVersion saves the given version to the cache, determines the smallest
|
// UploadLatestVersion saves the given version to the cache, determines the smallest
|
||||||
// TCB version in the cache among the last cacheWindowSize versions and updates
|
// TCB version in the cache among the last cacheWindowSize versions and updates
|
||||||
// the latest version in the API if there is an update.
|
// the latest version in the API if there is an update.
|
||||||
|
@ -90,7 +109,7 @@ func (c Client) UploadLatestVersion(
|
||||||
}
|
}
|
||||||
c.log.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate))
|
c.log.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate))
|
||||||
|
|
||||||
if !isInputNewerThanOtherVersion(attestationVariant, minVersion, latestVersionInAPI) {
|
if !IsInputNewerThanOtherVersion(attestationVariant, minVersion, latestVersionInAPI) {
|
||||||
c.log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI))
|
c.log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI))
|
||||||
return ErrNoNewerVersion
|
return ErrNoNewerVersion
|
||||||
}
|
}
|
||||||
|
@ -200,7 +219,7 @@ func findMinimalVersion[T attestationconfigapi.TDXVersion | attestationconfigapi
|
||||||
|
|
||||||
// If the current minimal version has newer versions than the one we just fetched,
|
// If the current minimal version has newer versions than the one we just fetched,
|
||||||
// update the minimal version to the older version.
|
// update the minimal version to the older version.
|
||||||
if isInputNewerThanOtherVersion(variant, *minimalVersion, obj.getVersion()) {
|
if IsInputNewerThanOtherVersion(variant, *minimalVersion, obj.getVersion()) {
|
||||||
v := obj.getVersion().(T)
|
v := obj.getVersion().(T)
|
||||||
minimalVersion = &v
|
minimalVersion = &v
|
||||||
minimalDate = date
|
minimalDate = date
|
||||||
|
@ -210,23 +229,6 @@ func findMinimalVersion[T attestationconfigapi.TDXVersion | attestationconfigapi
|
||||||
return *minimalVersion, minimalDate, nil
|
return *minimalVersion, minimalDate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInputNewerThanOtherVersion(variant variant.Variant, inputVersion, otherVersion any) bool {
|
|
||||||
var result bool
|
|
||||||
actionForVariant(variant,
|
|
||||||
func() {
|
|
||||||
input := inputVersion.(attestationconfigapi.TDXVersion)
|
|
||||||
other := otherVersion.(attestationconfigapi.TDXVersion)
|
|
||||||
result = isInputNewerThanOtherTDXVersion(input, other)
|
|
||||||
},
|
|
||||||
func() {
|
|
||||||
input := inputVersion.(attestationconfigapi.SEVSNPVersion)
|
|
||||||
other := otherVersion.(attestationconfigapi.SEVSNPVersion)
|
|
||||||
result = isInputNewerThanOtherSEVSNPVersion(input, other)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type apiVersionObject struct {
|
type apiVersionObject struct {
|
||||||
version string `json:"-"`
|
version string `json:"-"`
|
||||||
variant variant.Variant `json:"-"`
|
variant variant.Variant `json:"-"`
|
||||||
|
|
101
internal/api/attestationconfigapi/cli/compare.go
Normal file
101
internal/api/attestationconfigapi/cli/compare.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/verify"
|
||||||
|
"github.com/google/go-tdx-guest/proto/tdx"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCompareCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "compare VARIANT FILE [FILE...]",
|
||||||
|
Short: "Returns the minimum version of all given attestation reports.",
|
||||||
|
Long: "Compare a list of attestation reports and return the report with the minimum version.",
|
||||||
|
Example: "cli compare azure-sev-snp report1.json report2.json",
|
||||||
|
Args: cobra.MatchAll(cobra.MinimumNArgs(2), arg0isAttestationVariant()),
|
||||||
|
RunE: runCompare,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCompare(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
|
variant, err := variant.FromString(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing variant: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return compare(cmd, variant, args[1:], file.NewHandler(afero.NewOsFs()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(cmd *cobra.Command, attestationVariant variant.Variant, files []string, fs file.Handler) (retErr error) {
|
||||||
|
if !slices.Contains([]variant.Variant{variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}, variant.AzureTDX{}}, attestationVariant) {
|
||||||
|
return fmt.Errorf("variant %s not supported", attestationVariant)
|
||||||
|
}
|
||||||
|
|
||||||
|
lowestVersion, err := compareVersions(attestationVariant, files, fs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("comparing versions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println(lowestVersion)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareVersions(attestationVariant variant.Variant, files []string, fs file.Handler) (string, error) {
|
||||||
|
readReport := readSNPReport
|
||||||
|
if attestationVariant.Equal(variant.AzureTDX{}) {
|
||||||
|
readReport = readTDXReport
|
||||||
|
}
|
||||||
|
|
||||||
|
lowestVersion := files[0]
|
||||||
|
lowestReport, err := readReport(files[0], fs)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("reading tdx report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files[1:] {
|
||||||
|
report, err := readReport(file, fs)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("reading tdx report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.IsInputNewerThanOtherVersion(attestationVariant, lowestReport, report) {
|
||||||
|
lowestVersion = file
|
||||||
|
lowestReport = report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowestVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSNPReport(file string, fs file.Handler) (any, error) {
|
||||||
|
var report verify.Report
|
||||||
|
if err := fs.ReadJSON(file, &report); err != nil {
|
||||||
|
return nil, fmt.Errorf("reading snp report: %w", err)
|
||||||
|
}
|
||||||
|
return convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTDXReport(file string, fs file.Handler) (any, error) {
|
||||||
|
var report *tdx.QuoteV4
|
||||||
|
if err := fs.ReadJSON(file, &report); err != nil {
|
||||||
|
return nil, fmt.Errorf("reading tdx report: %w", err)
|
||||||
|
}
|
||||||
|
return convertQuoteToTDXVersion(report), nil
|
||||||
|
}
|
|
@ -26,11 +26,11 @@ import (
|
||||||
// newDeleteCmd creates the delete command.
|
// newDeleteCmd creates the delete command.
|
||||||
func newDeleteCmd() *cobra.Command {
|
func newDeleteCmd() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "delete {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} <version>",
|
Use: "delete VARIANT KIND <version>",
|
||||||
Short: "Delete an object from the attestationconfig API",
|
Short: "Delete an object from the attestationconfig API",
|
||||||
Long: "Delete a specific object version from the config api. <version> is the name of the object to delete (without .json suffix)",
|
Long: "Delete a specific object version from the config api. <version> is the name of the object to delete (without .json suffix)",
|
||||||
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure-sev-snp attestation-report 1.0.0",
|
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure-sev-snp attestation-report 1.0.0",
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)),
|
Args: cobra.MatchAll(cobra.ExactArgs(3), arg0isAttestationVariant(), isValidKind(1)),
|
||||||
PreRunE: envCheck,
|
PreRunE: envCheck,
|
||||||
RunE: runDelete,
|
RunE: runDelete,
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func newDeleteCmd() *cobra.Command {
|
||||||
Short: "delete all objects from the API path constellation/v1/attestation/<csp>",
|
Short: "delete all objects from the API path constellation/v1/attestation/<csp>",
|
||||||
Long: "Delete all objects from the API path constellation/v1/attestation/<csp>",
|
Long: "Delete all objects from the API path constellation/v1/attestation/<csp>",
|
||||||
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp",
|
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp",
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), isAttestationVariant(0)),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), arg0isAttestationVariant()),
|
||||||
RunE: runRecursiveDelete,
|
RunE: runRecursiveDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,11 @@ const (
|
||||||
distributionID = constants.CDNDefaultDistributionID
|
distributionID = constants.CDNDefaultDistributionID
|
||||||
envCosignPwd = "COSIGN_PASSWORD"
|
envCosignPwd = "COSIGN_PASSWORD"
|
||||||
envCosignPrivateKey = "COSIGN_PRIVATE_KEY"
|
envCosignPrivateKey = "COSIGN_PRIVATE_KEY"
|
||||||
// versionWindowSize defines the number of versions to be considered for the latest version. Each week 5 versions are uploaded for each node of the verify cluster.
|
// versionWindowSize defines the number of versions to be considered for the latest version.
|
||||||
versionWindowSize = 15
|
// Through our weekly e2e tests, each week 2 versions are uploaded:
|
||||||
|
// One from a stable release, and one from a debug image.
|
||||||
|
// A window size of 6 ensures we update only after a version has been "stable" for 3 weeks.
|
||||||
|
versionWindowSize = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -56,6 +59,7 @@ func newRootCmd() *cobra.Command {
|
||||||
|
|
||||||
rootCmd.AddCommand(newUploadCmd())
|
rootCmd.AddCommand(newUploadCmd())
|
||||||
rootCmd.AddCommand(newDeleteCmd())
|
rootCmd.AddCommand(newDeleteCmd())
|
||||||
|
rootCmd.AddCommand(newCompareCmd())
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
|
|
||||||
func newUploadCmd() *cobra.Command {
|
func newUploadCmd() *cobra.Command {
|
||||||
uploadCmd := &cobra.Command{
|
uploadCmd := &cobra.Command{
|
||||||
Use: "upload {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} <path>",
|
Use: "upload VARIANT KIND FILE",
|
||||||
Short: "Upload an object to the attestationconfig API",
|
Short: "Upload an object to the attestationconfig API",
|
||||||
|
|
||||||
Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first.\n"+
|
Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first.\n"+
|
||||||
|
@ -41,7 +41,7 @@ func newUploadCmd() *cobra.Command {
|
||||||
),
|
),
|
||||||
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure-sev-snp attestation-report /some/path/report.json",
|
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure-sev-snp attestation-report /some/path/report.json",
|
||||||
|
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)),
|
Args: cobra.MatchAll(cobra.ExactArgs(3), arg0isAttestationVariant(), isValidKind(1)),
|
||||||
PreRunE: envCheck,
|
PreRunE: envCheck,
|
||||||
RunE: runUpload,
|
RunE: runUpload,
|
||||||
}
|
}
|
||||||
|
@ -120,24 +120,20 @@ func uploadReport(
|
||||||
latestVersion = latestVersionInAPI.SEVSNPVersion
|
latestVersion = latestVersionInAPI.SEVSNPVersion
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path))
|
log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path))
|
||||||
var report verify.Report
|
newVersion, err = readSNPReport(cfg.path, fs)
|
||||||
if err := fs.ReadJSON(cfg.path, &report); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading snp report: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newVersion = convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB)
|
|
||||||
log.Info(fmt.Sprintf("Input SNP report: %+v", newVersion))
|
log.Info(fmt.Sprintf("Input SNP report: %+v", newVersion))
|
||||||
|
|
||||||
case variant.AzureTDX{}:
|
case variant.AzureTDX{}:
|
||||||
latestVersion = latestVersionInAPI.TDXVersion
|
latestVersion = latestVersionInAPI.TDXVersion
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("Reading TDX report from file: %s", cfg.path))
|
log.Info(fmt.Sprintf("Reading TDX report from file: %s", cfg.path))
|
||||||
var report *tdx.QuoteV4
|
newVersion, err = readTDXReport(cfg.path, fs)
|
||||||
if err := fs.ReadJSON(cfg.path, &report); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading tdx report: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newVersion = convertQuoteToTDXVersion(report)
|
|
||||||
log.Info(fmt.Sprintf("Input TDX report: %+v", newVersion))
|
log.Info(fmt.Sprintf("Input TDX report: %+v", newVersion))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -14,17 +15,17 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isAttestationVariant(arg int) cobra.PositionalArgs {
|
func arg0isAttestationVariant() cobra.PositionalArgs {
|
||||||
return func(_ *cobra.Command, args []string) error {
|
return func(_ *cobra.Command, args []string) error {
|
||||||
attestationVariant, err := variant.FromString(args[arg])
|
attestationVariant, err := variant.FromString(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("argument %s isn't a valid attestation variant", args[arg])
|
return errors.New("argument 0 isn't a valid attestation variant")
|
||||||
}
|
}
|
||||||
switch attestationVariant {
|
switch attestationVariant {
|
||||||
case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.AzureTDX{}, variant.GCPSEVSNP{}:
|
case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.AzureTDX{}, variant.GCPSEVSNP{}:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("argument %s isn't a supported attestation variant", args[arg])
|
return errors.New("argument 0 isn't a supported attestation variant")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,7 @@ func isAttestationVariant(arg int) cobra.PositionalArgs {
|
||||||
func isValidKind(arg int) cobra.PositionalArgs {
|
func isValidKind(arg int) cobra.PositionalArgs {
|
||||||
return func(_ *cobra.Command, args []string) error {
|
return func(_ *cobra.Command, args []string) error {
|
||||||
if kind := kindFromString(args[arg]); kind == unknown {
|
if kind := kindFromString(args[arg]); kind == unknown {
|
||||||
return fmt.Errorf("argument %s isn't a valid kind", args[arg])
|
return fmt.Errorf("argument %s isn't a valid kind: must be one of [%q, %q]", args[arg], attestationReport, guestFirmware)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue