mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-02 14:26:23 -04:00
config: automatically upload new Azure SNP versions to API + sign version with release key (#1854)
* sign version with release key and remove version from fetcher interface * extend azure-reporter GH action to upload updated version values to the Attestation API
This commit is contained in:
parent
18da9b8128
commit
a813760f96
14 changed files with 214 additions and 76 deletions
|
@ -46,12 +46,13 @@ func (i *IsolationTEE) PrintSVNs() {
|
|||
|
||||
func main() {
|
||||
configAPIExportPath := flag.String("export-path", "azure-sev-snp-version.json", "Path to the exported config API file.")
|
||||
maaJWT := flag.String("report", "", "MAA JWT report to verify")
|
||||
flag.Parse()
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage:", os.Args[0], "<maa-jwt>")
|
||||
if *maaJWT == "" {
|
||||
fmt.Println("Must provide --report")
|
||||
return
|
||||
}
|
||||
report, err := getTEEReport(os.Args[1])
|
||||
report, err := getTEEReport(*maaJWT)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "cmd",
|
||||
|
@ -8,7 +9,18 @@ go_library(
|
|||
deps = [
|
||||
"//internal/api/attestationconfig",
|
||||
"//internal/api/attestationconfig/client",
|
||||
"//internal/api/attestationconfig/fetcher",
|
||||
"//internal/staticupload",
|
||||
"@com_github_spf13_cobra//:cobra",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "cmd_test",
|
||||
srcs = ["root_test.go"],
|
||||
embed = [":cmd"],
|
||||
deps = [
|
||||
"//internal/api/attestationconfig",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -10,27 +10,32 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
attestationconfigclient "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
awsRegion = "eu-central-1"
|
||||
awsBucket = "cdn-constellation-backend"
|
||||
invalidDefault = 0
|
||||
envAwsKeyID = "AWS_ACCESS_KEY_ID"
|
||||
envAwsKey = "AWS_ACCESS_KEY"
|
||||
awsRegion = "eu-central-1"
|
||||
awsBucket = "cdn-constellation-backend"
|
||||
invalidDefault = 0
|
||||
envAwsKeyID = "AWS_ACCESS_KEY_ID"
|
||||
envAwsKey = "AWS_ACCESS_KEY"
|
||||
envCosignPwd = "COSIGN_PASSWORD"
|
||||
envCosignPrivateKey = "COSIGN_PRIVATE_KEY"
|
||||
)
|
||||
|
||||
var (
|
||||
versionFilePath string
|
||||
// Cosign credentials.
|
||||
cosignPwd string
|
||||
privateKeyPath string
|
||||
cosignPwd string
|
||||
privateKey string
|
||||
)
|
||||
|
||||
// Execute executes the root command.
|
||||
|
@ -41,58 +46,102 @@ func Execute() error {
|
|||
// newRootCmd creates the root command.
|
||||
func newRootCmd() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "AWS_ACCESS_KEY_ID=$ID AWS_ACCESS_KEY=$KEY upload -b 2 -t 0 -s 6 -m 93 --cosign-pwd $PWD --private-key $FILE_PATH",
|
||||
Use: "COSIGN_PASSWORD=$CPWD COSIGN_PRIVATE_KEY=$CKEY AWS_ACCESS_KEY_ID=$ID AWS_ACCESS_KEY=$KEY upload --version-file $FILE",
|
||||
Short: "Upload a set of versions specific to the azure-sev-snp attestation variant to the config api.",
|
||||
|
||||
Long: "Upload a set of versions specific to the azure-sev-snp attestation variant to the config api. Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) to be able to upload to S3.",
|
||||
RunE: runCmd,
|
||||
Long: fmt.Sprintf("Upload a set of versions specific to the azure-sev-snp attestation variant to the config api. Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign.", envCosignPrivateKey, envCosignPwd),
|
||||
PreRunE: envCheck,
|
||||
RunE: runCmd,
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVarP(&versionFilePath, "version-file", "f", "", "File path to the version json file.")
|
||||
rootCmd.PersistentFlags().StringVar(&cosignPwd, "cosign-pwd", "", "Cosign password used to decrpyt the private key.")
|
||||
rootCmd.PersistentFlags().StringVar(&privateKeyPath, "private-key", "", "File path of private key used to sign the payload.")
|
||||
must(enforceRequiredFlags(rootCmd, "version-file", "cosign-pwd", "private-key"))
|
||||
must(enforceRequiredFlags(rootCmd, "version-file"))
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func envCheck(_ *cobra.Command, _ []string) error {
|
||||
if os.Getenv(envCosignPrivateKey) == "" || os.Getenv(envCosignPwd) == "" {
|
||||
return fmt.Errorf("please set both %s and %s environment variables", envCosignPrivateKey, envCosignPwd)
|
||||
}
|
||||
cosignPwd = os.Getenv(envCosignPwd)
|
||||
privateKey = os.Getenv(envCosignPrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCmd(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
cfg := staticupload.Config{
|
||||
Bucket: awsBucket,
|
||||
Region: awsRegion,
|
||||
}
|
||||
privateKey, err := os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading private key: %w", err)
|
||||
}
|
||||
|
||||
versionBytes, err := os.ReadFile(versionFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading version file: %w", err)
|
||||
}
|
||||
var versions configapi.AzureSEVSNPVersion
|
||||
err = json.Unmarshal(versionBytes, &versions)
|
||||
if err != nil {
|
||||
var inputVersion attestationconfig.AzureSEVSNPVersion
|
||||
if err = json.Unmarshal(versionBytes, &inputVersion); err != nil {
|
||||
return fmt.Errorf("unmarshalling version file: %w", err)
|
||||
}
|
||||
|
||||
sut, sutClose, err := attestationconfigclient.New(ctx, cfg, []byte(cosignPwd), privateKey)
|
||||
latestAPIVersion, err := fetcher.New().FetchAzureSEVSNPVersionLatest(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating repo: %w", err)
|
||||
return fmt.Errorf("fetching latest version: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := sutClose(ctx); err != nil {
|
||||
fmt.Printf("closing repo: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := sut.UploadAzureSEVSNP(ctx, versions, time.Now()); err != nil {
|
||||
return fmt.Errorf("uploading version: %w", err)
|
||||
isNewer, err := isInputNewerThanLatestAPI(inputVersion, latestAPIVersion.AzureSEVSNPVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("comparing versions: %w", err)
|
||||
}
|
||||
if isNewer {
|
||||
fmt.Printf("Input version: %+v is newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
|
||||
sut, sutClose, err := attestationconfigclient.New(ctx, cfg, []byte(cosignPwd), []byte(privateKey))
|
||||
defer func() {
|
||||
if err := sutClose(ctx); err != nil {
|
||||
fmt.Printf("closing repo: %v\n", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating repo: %w", err)
|
||||
}
|
||||
|
||||
if err := sut.UploadAzureSEVSNP(ctx, inputVersion, time.Now()); err != nil {
|
||||
return fmt.Errorf("uploading version: %w", err)
|
||||
}
|
||||
cmd.Printf("Successfully uploaded new Azure SEV-SNP version: %+v\n", inputVersion)
|
||||
} else {
|
||||
cmd.Printf("Input version: %+v is not newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
|
||||
}
|
||||
cmd.Printf("Successfully uploaded new Azure SEV-SNP version: %+v\n", versions)
|
||||
return nil
|
||||
}
|
||||
|
||||
// isInputNewerThanLatestAPI compares all version fields with the latest API version and returns true if any input field is newer.
|
||||
func isInputNewerThanLatestAPI(input, latest attestationconfig.AzureSEVSNPVersion) (bool, error) {
|
||||
inputValues := reflect.ValueOf(input)
|
||||
latestValues := reflect.ValueOf(latest)
|
||||
fields := reflect.TypeOf(input)
|
||||
num := fields.NumField()
|
||||
// validate that no input field is smaller than latest
|
||||
for i := 0; i < num; i++ {
|
||||
field := fields.Field(i)
|
||||
inputValue := inputValues.Field(i).Uint()
|
||||
latestValue := latestValues.Field(i).Uint()
|
||||
if inputValue < latestValue {
|
||||
return false, fmt.Errorf("input %s version: %d is older than latest API version: %d", field.Name, inputValue, latestValue)
|
||||
} else if inputValue > latestValue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
// check if any input field is greater than latest
|
||||
for i := 0; i < num; i++ {
|
||||
inputValue := inputValues.Field(i).Uint()
|
||||
latestValue := latestValues.Field(i).Uint()
|
||||
if inputValue > latestValue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func enforceRequiredFlags(cmd *cobra.Command, flags ...string) error {
|
||||
for _, flag := range flags {
|
||||
if err := cmd.MarkPersistentFlagRequired(flag); err != nil {
|
||||
|
|
75
hack/configapi/cmd/root_test.go
Normal file
75
hack/configapi/cmd/root_test.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testCfg = attestationconfig.AzureSEVSNPVersion{
|
||||
Microcode: 93,
|
||||
TEE: 0,
|
||||
SNP: 6,
|
||||
Bootloader: 2,
|
||||
}
|
||||
|
||||
func TestIsInputNewerThanLatestAPI(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
latest attestationconfig.AzureSEVSNPVersion
|
||||
input attestationconfig.AzureSEVSNPVersion
|
||||
expect bool
|
||||
errMsg string
|
||||
}{
|
||||
"input is older than latest": {
|
||||
input: func(c attestationconfig.AzureSEVSNPVersion) attestationconfig.AzureSEVSNPVersion {
|
||||
c.Microcode--
|
||||
return c
|
||||
}(testCfg),
|
||||
latest: testCfg,
|
||||
expect: false,
|
||||
errMsg: "input Microcode version: 92 is older than latest API version: 93",
|
||||
},
|
||||
"input has greater and smaller version field than latest": {
|
||||
input: func(c attestationconfig.AzureSEVSNPVersion) attestationconfig.AzureSEVSNPVersion {
|
||||
c.Microcode++
|
||||
c.Bootloader--
|
||||
return c
|
||||
}(testCfg),
|
||||
latest: testCfg,
|
||||
expect: false,
|
||||
errMsg: "input Bootloader version: 1 is older than latest API version: 2",
|
||||
},
|
||||
"input is newer than latest": {
|
||||
input: func(c attestationconfig.AzureSEVSNPVersion) attestationconfig.AzureSEVSNPVersion {
|
||||
c.TEE++
|
||||
return c
|
||||
}(testCfg),
|
||||
latest: testCfg,
|
||||
expect: true,
|
||||
},
|
||||
"input is equal to latest": {
|
||||
input: testCfg,
|
||||
latest: testCfg,
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
isNewer, err := isInputNewerThanLatestAPI(tc.input, tc.latest)
|
||||
assert := assert.New(t)
|
||||
if tc.errMsg != "" {
|
||||
assert.EqualError(err, tc.errMsg)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expect, isNewer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue