config: enable azure snp version fetcher again + minimum age for latest version (#1899)

* fetch latest version when older than 2 weeks

* extend hack upload tool to pass an upload date

* Revert "config: disable user-facing version Azure SEV SNP fetch for v2.8  (#1882)"

This reverts commit c7b22d314a.

* fix tests

* use NewAzureSEVSNPVersionList for type guarantees

* Revert "use NewAzureSEVSNPVersionList for type guarantees"

This reverts commit 942566453f4b4a2b6dc16f8689248abf1dc47db4.

* assure list is sorted

* improve root.go style

* daniel feedback
This commit is contained in:
Adrian Stobbe 2023-06-09 12:48:12 +02:00 committed by GitHub
parent 72e168e653
commit 3fde118b33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 239 additions and 189 deletions

View File

@ -15,6 +15,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
@ -314,7 +315,7 @@ func (f stubAttestationFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ att
}, nil }, nil
} }
func (f stubAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (attestationconfigapi.AzureSEVSNPVersionAPI, error) { func (f stubAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context, _ time.Time) (attestationconfigapi.AzureSEVSNPVersionAPI, error) {
return attestationconfigapi.AzureSEVSNPVersionAPI{ return attestationconfigapi.AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: testCfg, AzureSEVSNPVersion: testCfg,
}, nil }, nil

View File

@ -56,6 +56,7 @@ func newRootCmd() *cobra.Command {
} }
rootCmd.Flags().StringVarP(&versionFilePath, "version-file", "f", "", "File path to the version json file.") rootCmd.Flags().StringVarP(&versionFilePath, "version-file", "f", "", "File path to the version json file.")
rootCmd.Flags().BoolVar(&force, "force", false, "force to upload version regardless of comparison to latest API value.") rootCmd.Flags().BoolVar(&force, "force", false, "force to upload version regardless of comparison to latest API value.")
rootCmd.Flags().StringP("upload-date", "d", "", "upload a version with this date as version name. Setting it implies --force.")
must(enforceRequiredFlags(rootCmd, "version-file")) must(enforceRequiredFlags(rootCmd, "version-file"))
rootCmd.AddCommand(newDeleteCmd()) rootCmd.AddCommand(newDeleteCmd())
return rootCmd return rootCmd
@ -85,21 +86,40 @@ func runCmd(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("unmarshalling version file: %w", err) return fmt.Errorf("unmarshalling version file: %w", err)
} }
latestAPIVersion, err := attestationconfigapi.NewFetcher().FetchAzureSEVSNPVersionLatest(ctx) dateStr, err := cmd.Flags().GetString("upload-date")
if err != nil { if err != nil {
return fmt.Errorf("fetching latest version: %w", err) return fmt.Errorf("getting upload date: %w", err)
}
var uploadDate time.Time
if dateStr != "" {
uploadDate, err = time.Parse("2006-01-01-01-01", dateStr)
if err != nil {
return fmt.Errorf("parsing date: %w", err)
}
} else {
uploadDate = time.Now()
force = true
} }
isNewer, err := isInputNewerThanLatestAPI(inputVersion, latestAPIVersion.AzureSEVSNPVersion) doUpload := false
if err != nil { if !force {
return fmt.Errorf("comparing versions: %w", err) latestAPIVersion, err := attestationconfigapi.NewFetcher().FetchAzureSEVSNPVersionLatest(ctx, time.Now())
} if err != nil {
if isNewer || force { return fmt.Errorf("fetching latest version: %w", err)
if force {
cmd.Println("Forcing upload of new version")
} else {
cmd.Printf("Input version: %+v is newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
} }
isNewer, err := isInputNewerThanLatestAPI(inputVersion, latestAPIVersion.AzureSEVSNPVersion)
if err != nil {
return fmt.Errorf("comparing versions: %w", err)
}
cmd.Print(versionComparisonInformation(isNewer, inputVersion, latestAPIVersion.AzureSEVSNPVersion))
doUpload = isNewer
} else {
doUpload = true
cmd.Println("Forcing upload of new version")
}
if doUpload {
sut, sutClose, err := attestationconfigapi.NewClient(ctx, cfg, []byte(cosignPwd), []byte(privateKey), false, log()) sut, sutClose, err := attestationconfigapi.NewClient(ctx, cfg, []byte(cosignPwd), []byte(privateKey), false, log())
defer func() { defer func() {
if err := sutClose(ctx); err != nil { if err := sutClose(ctx); err != nil {
@ -110,16 +130,21 @@ func runCmd(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("creating repo: %w", err) return fmt.Errorf("creating repo: %w", err)
} }
if err := sut.UploadAzureSEVSNP(ctx, inputVersion, time.Now()); err != nil { if err := sut.UploadAzureSEVSNP(ctx, inputVersion, uploadDate); err != nil {
return fmt.Errorf("uploading version: %w", err) return fmt.Errorf("uploading version: %w", err)
} }
cmd.Printf("Successfully uploaded new Azure SEV-SNP version: %+v\n", inputVersion) 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)
} }
return nil return nil
} }
func versionComparisonInformation(isNewer bool, inputVersion attestationconfigapi.AzureSEVSNPVersion, latestAPIVersion attestationconfigapi.AzureSEVSNPVersion) string {
if isNewer {
return fmt.Sprintf("Input version: %+v is newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
}
return fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
}
// isInputNewerThanLatestAPI compares all version fields with the latest API version and returns true if any input field is newer. // isInputNewerThanLatestAPI compares all version fields with the latest API version and returns true if any input field is newer.
func isInputNewerThanLatestAPI(input, latest attestationconfigapi.AzureSEVSNPVersion) (bool, error) { func isInputNewerThanLatestAPI(input, latest attestationconfigapi.AzureSEVSNPVersion) (bool, error) {
inputValues := reflect.ValueOf(input) inputValues := reflect.ValueOf(input)

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"path" "path"
"sort"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -110,6 +111,11 @@ func (i AzureSEVSNPVersionList) ValidateRequest() error {
return nil return nil
} }
// SortAzureSEVSNPVersionList sorts the list of versions in reverse order.
func SortAzureSEVSNPVersionList(versions AzureSEVSNPVersionList) {
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
}
// Validate validates the response. // Validate validates the response.
func (i AzureSEVSNPVersionList) Validate() error { func (i AzureSEVSNPVersionList) Validate() error {
if len(i) < 1 { if len(i) < 1 {

View File

@ -9,7 +9,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort"
"time" "time"
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client" apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
@ -43,7 +42,7 @@ func NewClient(ctx context.Context, cfg staticupload.Config, cosignPwd, privateK
return repo, clientClose, nil return repo, clientClose, nil
} }
// UploadAzureSEVSNP uploads the latest version numbers of the Azure SEVSNP. // UploadAzureSEVSNP uploads the latest version numbers of the Azure SEVSNP. Then version name is the UTC timestamp of the date. The /list entry stores the version name + .json suffix.
func (a Client) UploadAzureSEVSNP(ctx context.Context, version AzureSEVSNPVersion, date time.Time) error { func (a Client) UploadAzureSEVSNP(ctx context.Context, version AzureSEVSNPVersion, date time.Time) error {
versions, err := a.List(ctx, variant.AzureSEVSNP{}) versions, err := a.List(ctx, variant.AzureSEVSNP{})
if err != nil { if err != nil {
@ -181,6 +180,6 @@ func executeAllCmds(ctx context.Context, client *apiclient.Client, cmds []crudCm
func addVersion(versions []string, newVersion string) []string { func addVersion(versions []string, newVersion string) []string {
versions = append(versions, newVersion) versions = append(versions, newVersion)
versions = variant.RemoveDuplicate(versions) versions = variant.RemoveDuplicate(versions)
sort.Sort(sort.Reverse(sort.StringSlice(versions))) SortAzureSEVSNPVersionList(versions)
return versions return versions
} }

View File

@ -10,24 +10,30 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher" apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/edgelesssys/constellation/v2/internal/sigstore"
) )
// minimumAgeVersion is the minimum age to accept the version as latest.
const minimumAgeVersion = 14 * 24 * time.Hour
const cosignPublicKey = constants.CosignPublicKeyReleases const cosignPublicKey = constants.CosignPublicKeyReleases
// Fetcher fetches config API resources without authentication. // Fetcher fetches config API resources without authentication.
type Fetcher interface { type Fetcher interface {
FetchAzureSEVSNPVersion(ctx context.Context, azureVersion AzureSEVSNPVersionAPI) (AzureSEVSNPVersionAPI, error) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion AzureSEVSNPVersionAPI) (AzureSEVSNPVersionAPI, error)
FetchAzureSEVSNPVersionList(ctx context.Context, attestation AzureSEVSNPVersionList) (AzureSEVSNPVersionList, error) FetchAzureSEVSNPVersionList(ctx context.Context, attestation AzureSEVSNPVersionList) (AzureSEVSNPVersionList, error)
FetchAzureSEVSNPVersionLatest(ctx context.Context) (AzureSEVSNPVersionAPI, error) FetchAzureSEVSNPVersionLatest(ctx context.Context, now time.Time) (AzureSEVSNPVersionAPI, error)
} }
// fetcher fetches AttestationCfg API resources without authentication. // fetcher fetches AttestationCfg API resources without authentication.
type fetcher struct { type fetcher struct {
apifetcher.HTTPClient apifetcher.HTTPClient
verifier sigstore.Verifier
} }
// NewFetcher returns a new apifetcher. // NewFetcher returns a new apifetcher.
@ -37,7 +43,11 @@ func NewFetcher() Fetcher {
// NewFetcherWithClient returns a new fetcher with custom http client. // NewFetcherWithClient returns a new fetcher with custom http client.
func NewFetcherWithClient(client apifetcher.HTTPClient) Fetcher { func NewFetcherWithClient(client apifetcher.HTTPClient) Fetcher {
return &fetcher{client} return newFetcherWithClientAndVerifier(client, sigstore.CosignVerifier{})
}
func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifier sigstore.Verifier) Fetcher {
return &fetcher{client, cosignVerifier}
} }
// FetchAzureSEVSNPVersionList fetches the version list information from the config API. // FetchAzureSEVSNPVersionList fetches the version list information from the config API.
@ -63,7 +73,7 @@ func (f *fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion Azur
return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", azureVersion.Version, err) return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", azureVersion.Version, err)
} }
err = sigstore.CosignVerifier{}.VerifySignature(versionBytes, signature.Signature, []byte(cosignPublicKey)) err = f.verifier.VerifySignature(versionBytes, signature.Signature, []byte(cosignPublicKey))
if err != nil { if err != nil {
return fetchedVersion, fmt.Errorf("verify version %s signature: %w", azureVersion.Version, err) return fetchedVersion, fmt.Errorf("verify version %s signature: %w", azureVersion.Version, err)
} }
@ -71,16 +81,34 @@ func (f *fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion Azur
} }
// FetchAzureSEVSNPVersionLatest returns the latest versions of the given type. // FetchAzureSEVSNPVersionLatest returns the latest versions of the given type.
func (f *fetcher) FetchAzureSEVSNPVersionLatest(ctx context.Context) (res AzureSEVSNPVersionAPI, err error) { func (f *fetcher) FetchAzureSEVSNPVersionLatest(ctx context.Context, now time.Time) (res AzureSEVSNPVersionAPI, err error) {
var list AzureSEVSNPVersionList var list AzureSEVSNPVersionList
list, err = f.FetchAzureSEVSNPVersionList(ctx, list) list, err = f.FetchAzureSEVSNPVersionList(ctx, list)
if err != nil { if err != nil {
return res, fmt.Errorf("fetching versions list: %w", err) return res, fmt.Errorf("fetching versions list: %w", err)
} }
get := AzureSEVSNPVersionAPI{Version: list[0]} // get latest version (as sorted reversely alphanumerically) getVersionRequest, err := getLatestVersionOlderThanMinimumAge(list, now, minimumAgeVersion)
get, err = f.FetchAzureSEVSNPVersion(ctx, get) if err != nil {
return res, fmt.Errorf("finding latest valid version: %w", err)
}
res, err = f.FetchAzureSEVSNPVersion(ctx, getVersionRequest)
if err != nil { if err != nil {
return res, fmt.Errorf("fetching version: %w", err) return res, fmt.Errorf("fetching version: %w", err)
} }
return get, nil return
}
func getLatestVersionOlderThanMinimumAge(list AzureSEVSNPVersionList, now time.Time, minimumAgeVersion time.Duration) (AzureSEVSNPVersionAPI, error) {
SortAzureSEVSNPVersionList(list)
for _, v := range list {
dateStr := strings.TrimSuffix(v, ".json")
versionDate, err := time.Parse("2006-01-01-01-01", dateStr)
if err != nil {
return AzureSEVSNPVersionAPI{}, fmt.Errorf("parsing version date %s: %w", dateStr, err)
}
if now.Sub(versionDate) > minimumAgeVersion {
return AzureSEVSNPVersionAPI{Version: v}, nil
}
}
return AzureSEVSNPVersionAPI{}, fmt.Errorf("no valid version fulfilling minimum age found")
} }

View File

@ -13,11 +13,56 @@ import (
"io" "io"
"net/http" "net/http"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var testCfg = AzureSEVSNPVersionAPI{ func TestFetchLatestAzureSEVSNPVersion(t *testing.T) {
now := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
testcases := map[string]struct {
fetcherVersions []string
timeAtTest time.Time
wantErr bool
want AzureSEVSNPVersionAPI
}{
"get latest version if older than 2 weeks": {
fetcherVersions: []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"},
timeAtTest: now.Add(days(15)),
want: latestVersion,
},
"get older version if latest version is not older than minimum age": {
fetcherVersions: []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"},
timeAtTest: now.Add(days(7)),
want: olderVersion,
},
"fail when no version is older minimum age": {
fetcherVersions: []string{"2021-01-01-01-01.json", "2020-12-31-00-00.json"},
timeAtTest: now.Add(days(2)),
wantErr: true,
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
client := &http.Client{
Transport: &fakeConfigAPIHandler{
versions: tc.fetcherVersions,
},
}
fetcher := newFetcherWithClientAndVerifier(client, dummyVerifier{})
res, err := fetcher.FetchAzureSEVSNPVersionLatest(context.Background(), tc.timeAtTest)
assert := assert.New(t)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, res)
}
})
}
}
var latestVersion = AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: AzureSEVSNPVersion{ AzureSEVSNPVersion: AzureSEVSNPVersion{
Microcode: 93, Microcode: 93,
TEE: 0, TEE: 0,
@ -26,52 +71,29 @@ var testCfg = AzureSEVSNPVersionAPI{
}, },
} }
func TestFetchLatestAzureSEVSNPVersion(t *testing.T) { var olderVersion = AzureSEVSNPVersionAPI{
testcases := map[string]struct { AzureSEVSNPVersion: AzureSEVSNPVersion{
signature []byte Microcode: 1,
wantErr bool TEE: 0,
want AzureSEVSNPVersionAPI SNP: 1,
}{ Bootloader: 1,
"get version with valid signature": { },
signature: []byte("MEQCIBPEbYg89MIQuaGStLhKGLGMKvKFoYCaAniDLwoIwulqAiB+rj7KMaMOMGxmUsjI7KheCXSNM8NzN+tuDw6AywI75A=="), // signed with release key }
want: testCfg,
},
"fail with invalid signature": {
signature: []byte("invalid"),
wantErr: true,
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
client := &http.Client{
Transport: &fakeConfigAPIHandler{
signature: tc.signature,
},
}
fetcher := NewFetcherWithClient(client)
res, err := fetcher.FetchAzureSEVSNPVersionLatest(context.Background())
assert := assert.New(t) func days(days int) time.Duration {
if tc.wantErr { return time.Duration(days*24) * time.Hour
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(testCfg, res)
}
})
}
} }
type fakeConfigAPIHandler struct { type fakeConfigAPIHandler struct {
signature []byte versions []string
} }
// RoundTrip resolves the request and returns a dummy response. // RoundTrip resolves the request and returns a dummy response.
func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) { func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) {
signature := []byte("placeholderSignature")
if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/list" { if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/list" {
res := &http.Response{} res := &http.Response{}
data := []string{"2021-01-01-01-01.json", "2019-01-01-01-02.json"} // return multiple versions to check that latest version is correctly selected bt, err := json.Marshal(f.versions)
bt, err := json.Marshal(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,7 +104,17 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
return res, nil return res, nil
} else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2021-01-01-01-01.json" { } else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2021-01-01-01-01.json" {
res := &http.Response{} res := &http.Response{}
bt, err := json.Marshal(testCfg) bt, err := json.Marshal(latestVersion)
if err != nil {
return nil, err
}
res.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK
return res, nil
} else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2019-01-01-01-01.json" {
res := &http.Response{}
bt, err := json.Marshal(olderVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -93,7 +125,20 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
} else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2021-01-01-01-01.json.sig" { } else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2021-01-01-01-01.json.sig" {
res := &http.Response{} res := &http.Response{}
obj := AzureSEVSNPVersionSignature{ obj := AzureSEVSNPVersionSignature{
Signature: f.signature, Signature: signature,
}
bt, err := json.Marshal(obj)
if err != nil {
return nil, err
}
res.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK
return res, nil
} else if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/2019-01-01-01-01.json.sig" {
res := &http.Response{}
obj := AzureSEVSNPVersionSignature{
Signature: signature,
} }
bt, err := json.Marshal(obj) bt, err := json.Marshal(obj)
if err != nil { if err != nil {
@ -106,3 +151,9 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
} }
return nil, errors.New("no endpoint found") return nil, errors.New("no endpoint found")
} }
type dummyVerifier struct{}
func (s dummyVerifier) VerifySignature(_, _, _ []byte) error {
return nil
}

View File

@ -218,10 +218,6 @@ func TestTrustedKeyFromSNP(t *testing.T) {
AcceptedKeyDigests: tc.idkeydigests, AcceptedKeyDigests: tc.idkeydigests,
EnforcementPolicy: tc.enforceIDKeyDigest, EnforcementPolicy: tc.enforceIDKeyDigest,
} }
cfg.BootloaderVersion = config.AttestationVersion{Value: 2}
cfg.TEEVersion = config.AttestationVersion{Value: 0}
cfg.MicrocodeVersion = config.AttestationVersion{Value: 93}
cfg.SNPVersion = config.AttestationVersion{Value: 6}
validator := &Validator{ validator := &Validator{
hclValidator: &instanceInfo, hclValidator: &instanceInfo,
@ -353,12 +349,6 @@ func TestNewSNPReportFromBytes(t *testing.T) {
}, },
} }
cfg := config.DefaultForAzureSEVSNP() cfg := config.DefaultForAzureSEVSNP()
cfg.BootloaderVersion = config.AttestationVersion{Value: 2}
cfg.TEEVersion = config.AttestationVersion{Value: 0}
cfg.MicrocodeVersion = config.AttestationVersion{Value: 93}
cfg.SNPVersion = config.AttestationVersion{Value: 6}
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)

View File

@ -8,7 +8,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strings" "strings"
) )
@ -34,7 +33,7 @@ func (v AttestationVersion) MarshalYAML() (any, error) {
if v.IsLatest { if v.IsLatest {
return "latest", nil return "latest", nil
} }
return int(v.Value), nil return v.Value, nil
} }
// UnmarshalYAML implements a custom unmarshaller to resolve "atest" values. // UnmarshalYAML implements a custom unmarshaller to resolve "atest" values.
@ -68,12 +67,11 @@ func (v *AttestationVersion) parseRawUnmarshal(rawUnmarshal any) error {
switch s := rawUnmarshal.(type) { switch s := rawUnmarshal.(type) {
case string: case string:
if strings.ToLower(s) == "latest" { if strings.ToLower(s) == "latest" {
// TODO(elchead): activate latest logic for next release AB#3036 v.IsLatest = true
return errors.New("latest is not supported as a version value") v.Value = placeholderVersionValue
// v.IsLatest = true } else {
// v.Value = placeholderVersionValue return fmt.Errorf("invalid version value: %s", s)
} }
return fmt.Errorf("invalid version value: %s", s)
case int: case int:
v.Value = uint8(s) v.Value = uint8(s)
// yaml spec allows "1" as float64, so version number might come as a float: https://github.com/go-yaml/yaml/issues/430 // yaml spec allows "1" as float64, so version number might come as a float: https://github.com/go-yaml/yaml/issues/430

View File

@ -9,7 +9,6 @@ package config
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -45,39 +44,3 @@ func TestVersionMarshalYAML(t *testing.T) {
}) })
} }
} }
func TestVersionUnmarshalYAML(t *testing.T) {
tests := []struct {
name string
expected AttestationVersion
yamlInput string
wantErr bool
}{
// TODO(elchead): activate latest logic for next release AB#3036
{
name: "latest value is not allowed",
expected: AttestationVersion{},
yamlInput: "latest\n",
wantErr: true,
},
{
name: "value 5 resolves to 5",
expected: AttestationVersion{
Value: 5,
},
yamlInput: "5\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sut := AttestationVersion{}
err := yaml.Unmarshal([]byte(tt.yamlInput), &sut)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.expected.IsLatest, sut.IsLatest)
})
}
}

View File

@ -27,6 +27,7 @@ import (
"os" "os"
"reflect" "reflect"
"strings" "strings"
"time"
"github.com/go-playground/locales/en" "github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
@ -386,19 +387,18 @@ func fromFile(fileHandler file.Handler, name string) (*Config, error) {
// 2. For "latest" version values of the attestation variants fetch the version numbers. // 2. For "latest" version values of the attestation variants fetch the version numbers.
// 3. Read secrets from environment variables. // 3. Read secrets from environment variables.
// 4. Validate config. If `--force` is set the version validation will be disabled and any version combination is allowed. // 4. Validate config. If `--force` is set the version validation will be disabled and any version combination is allowed.
func New(fileHandler file.Handler, name string, _ attestationconfigapi.Fetcher, force bool) (*Config, error) { func New(fileHandler file.Handler, name string, fetcher attestationconfigapi.Fetcher, force bool) (*Config, error) {
// Read config file // Read config file
c, err := fromFile(fileHandler, name) c, err := fromFile(fileHandler, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(elchead): activate latest logic for next release AB#3036 if azure := c.Attestation.AzureSEVSNP; azure != nil {
//if azure := c.Attestation.AzureSEVSNP; azure != nil { if err := azure.FetchAndSetLatestVersionNumbers(context.Background(), fetcher, time.Now()); err != nil {
// if err := azure.FetchAndSetLatestVersionNumbers(fetcher); err != nil { return c, err
// return c, err }
// } }
//}
// Read secrets from env-vars. // Read secrets from env-vars.
clientSecretValue := os.Getenv(constants.EnvVarAzureClientSecretValue) clientSecretValue := os.Getenv(constants.EnvVarAzureClientSecretValue)
@ -925,19 +925,12 @@ type AzureSEVSNP struct {
// DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation. // DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation.
// Version numbers have placeholder values and the latest available values can be fetched using [AzureSEVSNP.FetchAndSetLatestVersionNumbers]. // Version numbers have placeholder values and the latest available values can be fetched using [AzureSEVSNP.FetchAndSetLatestVersionNumbers].
func DefaultForAzureSEVSNP() *AzureSEVSNP { func DefaultForAzureSEVSNP() *AzureSEVSNP {
// TODO(elchead): activate latest logic for next release AB#3036
azureSNPCfg := attestationconfigapi.AzureSEVSNPVersion{
Bootloader: 3,
TEE: 0,
SNP: 8,
Microcode: 115,
}
return &AzureSEVSNP{ return &AzureSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureSEVSNP{}), Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureSEVSNP{}),
BootloaderVersion: AttestationVersion{Value: azureSNPCfg.Bootloader}, // NewLatestPlaceholderVersion(), BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: AttestationVersion{Value: azureSNPCfg.TEE}, // NewLatestPlaceholderVersion(), TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: AttestationVersion{Value: azureSNPCfg.SNP}, // NewLatestPlaceholderVersion(), SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: AttestationVersion{Value: azureSNPCfg.Microcode}, // NewLatestPlaceholderVersion(), MicrocodeVersion: NewLatestPlaceholderVersion(),
FirmwareSignerConfig: SNPFirmwareSignerConfig{ FirmwareSignerConfig: SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.DefaultList(), AcceptedKeyDigests: idkeydigest.DefaultList(),
EnforcementPolicy: idkeydigest.MAAFallback, EnforcementPolicy: idkeydigest.MAAFallback,
@ -981,8 +974,8 @@ func (c AzureSEVSNP) EqualTo(old AttestationCfg) (bool, error) {
} }
// FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them. // FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them.
func (c *AzureSEVSNP) FetchAndSetLatestVersionNumbers(fetcher attestationconfigapi.Fetcher) error { func (c *AzureSEVSNP) FetchAndSetLatestVersionNumbers(ctx context.Context, fetcher attestationconfigapi.Fetcher, now time.Time) error {
versions, err := fetcher.FetchAzureSEVSNPVersionLatest(context.Background()) versions, err := fetcher.FetchAzureSEVSNPVersionLatest(ctx, now)
if err != nil { if err != nil {
return err return err
} }

View File

@ -11,6 +11,7 @@ import (
"errors" "errors"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/go-playground/locales/en" "github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
@ -41,21 +42,20 @@ func TestDefaultConfig(t *testing.T) {
assert.NotNil(def) assert.NotNil(def)
} }
// TODO(elchead): activate latest logic for next release AB#3036 func TestDefaultConfigWritesLatestVersion(t *testing.T) {
// func TestDefaultConfigWritesLatestVersion(t *testing.T) { conf := Default()
// conf := Default() bt, err := yaml.Marshal(conf)
// bt, err := yaml.Marshal(conf) require := require.New(t)
// require := require.New(t) require.NoError(err)
// require.NoError(err)
// var mp configMap var mp configMap
// require.NoError(yaml.Unmarshal(bt, &mp)) require.NoError(yaml.Unmarshal(bt, &mp))
// assert := assert.New(t) assert := assert.New(t)
// assert.Equal("latest", mp.getAzureSEVSNPVersion("microcodeVersion")) assert.Equal("latest", mp.getAzureSEVSNPVersion("microcodeVersion"))
// assert.Equal("latest", mp.getAzureSEVSNPVersion("teeVersion")) assert.Equal("latest", mp.getAzureSEVSNPVersion("teeVersion"))
// assert.Equal("latest", mp.getAzureSEVSNPVersion("snpVersion")) assert.Equal("latest", mp.getAzureSEVSNPVersion("snpVersion"))
// assert.Equal("latest", mp.getAzureSEVSNPVersion("bootloaderVersion")) assert.Equal("latest", mp.getAzureSEVSNPVersion("bootloaderVersion"))
//} }
func TestReadConfigFile(t *testing.T) { func TestReadConfigFile(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
@ -64,41 +64,29 @@ func TestReadConfigFile(t *testing.T) {
wantResult *Config wantResult *Config
wantErr bool wantErr bool
}{ }{
// TODO(elchead): activate latest logic for next release AB#3036 "mix of Latest and uint as version value": {
//"mix of Latest and uint as version value": {
// config: func() configMap {
// conf := Default()
// m := getConfigAsMap(conf, t)
// m.setAzureSEVSNPVersion("microcodeVersion", "Latest") // check uppercase also works
// m.setAzureSEVSNPVersion("teeVersion", 2)
// m.setAzureSEVSNPVersion("bootloaderVersion", 1)
// return m
// }(),
// configName: constants.ConfigFilename,
// wantResult: func() *Config {
// conf := Default()
// conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion{
// Value: 1,
// IsLatest: false,
// }
// conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion{
// Value: 2,
// IsLatest: false,
// }
// return conf
// }(),
//},
// TODO(elchead): activate latest logic for next release AB#3036
"refuse invalid latest value": {
config: func() configMap { config: func() configMap {
conf := Default() conf := Default()
m := getConfigAsMap(conf, t) m := getConfigAsMap(conf, t)
m.setAzureSEVSNPVersion("microcodeVersion", "latest") m.setAzureSEVSNPVersion("microcodeVersion", "Latest") // check uppercase also works
m.setAzureSEVSNPVersion("teeVersion", 2)
m.setAzureSEVSNPVersion("bootloaderVersion", 1)
return m return m
}(), }(),
configName: constants.ConfigFilename, configName: constants.ConfigFilename,
wantErr: true, wantResult: func() *Config {
conf := Default()
conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion{
Value: 1,
IsLatest: false,
}
conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion{
Value: 2,
IsLatest: false,
}
return conf
}(),
}, },
"refuse invalid version value": { "refuse invalid version value": {
config: func() configMap { config: func() configMap {
@ -271,7 +259,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
} }
// Test // Test
c, err := New(fileHandler, constants.ConfigFilename, fakeConfigFetcher{}, false) c, err := New(fileHandler, constants.ConfigFilename, stubAttestationFetcher{}, false)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -889,9 +877,9 @@ func (c configMap) setAzureSEVSNPVersion(versionType string, value interface{})
c["attestation"].(configMap)["azureSEVSNP"].(configMap)[versionType] = value c["attestation"].(configMap)["azureSEVSNP"].(configMap)[versionType] = value
} }
//func (c configMap) getAzureSEVSNPVersion(versionType string) interface{} { func (c configMap) getAzureSEVSNPVersion(versionType string) interface{} {
// return c["attestation"].(configMap)["azureSEVSNP"].(configMap)[versionType] return c["attestation"].(configMap)["azureSEVSNP"].(configMap)[versionType]
//} }
// getConfigAsMap returns a map of the config. // getConfigAsMap returns a map of the config.
func getConfigAsMap(conf *Config, t *testing.T) (res configMap) { func getConfigAsMap(conf *Config, t *testing.T) (res configMap) {
@ -905,21 +893,21 @@ func getConfigAsMap(conf *Config, t *testing.T) (res configMap) {
return return
} }
type fakeConfigFetcher struct{} type stubAttestationFetcher struct{}
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ configapi.AzureSEVSNPVersionList) (configapi.AzureSEVSNPVersionList, error) { func (f stubAttestationFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ configapi.AzureSEVSNPVersionList) (configapi.AzureSEVSNPVersionList, error) {
return configapi.AzureSEVSNPVersionList( return configapi.AzureSEVSNPVersionList(
[]string{}, []string{},
), nil ), nil
} }
func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configapi.AzureSEVSNPVersionAPI) (configapi.AzureSEVSNPVersionAPI, error) { func (f stubAttestationFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configapi.AzureSEVSNPVersionAPI) (configapi.AzureSEVSNPVersionAPI, error) {
return configapi.AzureSEVSNPVersionAPI{ return configapi.AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: testCfg, AzureSEVSNPVersion: testCfg,
}, nil }, nil
} }
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (configapi.AzureSEVSNPVersionAPI, error) { func (f stubAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context, _ time.Time) (configapi.AzureSEVSNPVersionAPI, error) {
return configapi.AzureSEVSNPVersionAPI{ return configapi.AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: testCfg, AzureSEVSNPVersion: testCfg,
}, nil }, nil

View File

@ -18,6 +18,11 @@ import (
sigsig "github.com/sigstore/sigstore/pkg/signature" sigsig "github.com/sigstore/sigstore/pkg/signature"
) )
// Verifier checks if the signature of content can be verified.
type Verifier interface {
VerifySignature(content, signature, publicKey []byte) error
}
// CosignVerifier checks if the signature of content can be verified // CosignVerifier checks if the signature of content can be verified
// using a cosign public key. // using a cosign public key.
type CosignVerifier struct{} type CosignVerifier struct{}

View File

@ -129,6 +129,9 @@ While this API should stay compatible with old release, extensive changes to our
In this case a new API version will be used to retrieve the config in the updated format, e.g. `/constellation/v2/attestation/<ATTESTATION_VARIANT>/`. In this case a new API version will be used to retrieve the config in the updated format, e.g. `/constellation/v2/attestation/<ATTESTATION_VARIANT>/`.
The old API will still receive updates for at least the next release cycle, during this time this API version will also return a deprecation warning when requesting `list`. The old API will still receive updates for at least the next release cycle, during this time this API version will also return a deprecation warning when requesting `list`.
### Azure SEV-SNP
IMPORTANT: Since the current version fetches from the Azure SEV-SNP report are not guaranteed to be globally rolled out at the time of the report, we introduce a minimum age (2 weeks) of the version to consider it a valid latest version.
This validation is only enforced on the fetcher side! This means that the HTTP endpoints contain all versions, even those that do not yet have the minimum age.
### AWS ### AWS
AWS provides a way to precalculate launch-measurements for their firmware in SEV-SNP CVMs. AWS provides a way to precalculate launch-measurements for their firmware in SEV-SNP CVMs.