Implement support for "latest" placeholders for Azure TDX

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2024-06-12 10:24:16 +02:00 committed by Daniel Weiße
parent a34493caa6
commit 9159b60331
16 changed files with 410 additions and 267 deletions

View File

@ -6,7 +6,7 @@ go_library(
srcs = [
"attestationconfigapi.go",
"fetcher.go",
"snp.go",
"version.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi",
visibility = ["//:__subpackages__"],
@ -22,7 +22,7 @@ go_test(
name = "attestationconfigapi_test",
srcs = [
"fetcher_test.go",
"snp_test.go",
"version_test.go",
],
embed = [":attestationconfigapi"],
deps = [

View File

@ -20,6 +20,7 @@ go_library(
deps = [
"//internal/api/attestationconfigapi",
"//internal/api/attestationconfigapi/cli/client",
"//internal/api/fetcher",
"//internal/attestation/variant",
"//internal/cloud/cloudprovider",
"//internal/constants",

View File

@ -22,27 +22,27 @@ const AttestationURLPath = "constellation/v1/attestation"
// SEVSNPVersion tracks the latest version of each component for SEV-SNP.
type SEVSNPVersion struct {
// Bootloader is the latest version of the SEV-SNP bootloader.
Bootloader uint8 `json:"bootloader,omitempty"`
Bootloader uint8 `json:"bootloader"`
// TEE is the latest version of the SEV-SNP TEE.
TEE uint8 `json:"tee,omitempty"`
TEE uint8 `json:"tee"`
// SNP is the latest version of the SEV-SNP SNP.
SNP uint8 `json:"snp,omitempty"`
SNP uint8 `json:"snp"`
// Microcode is the latest version of the SEV-SNP microcode.
Microcode uint8 `json:"microcode,omitempty"`
Microcode uint8 `json:"microcode"`
}
// TDXVersion tracks the latest version of each component for TDX.
type TDXVersion struct {
// QESVN is the latest QE security version number.
QESVN uint16 `json:"qeSVN,omitempty"`
QESVN uint16 `json:"qeSVN"`
// PCESVN is the latest PCE security version number.
PCESVN uint16 `json:"pceSVN,omitempty"`
PCESVN uint16 `json:"pceSVN"`
// TEETCBSVN are the latest component-wise security version numbers for the TEE.
TEETCBSVN [16]byte `json:"teeTCBSVN,omitempty"`
TEETCBSVN [16]byte `json:"teeTCBSVN"`
// QEVendorID is the latest QE vendor ID.
QEVendorID [16]byte `json:"qeVendorID,omitempty"`
QEVendorID [16]byte `json:"qeVendorID"`
// XFAM is the latest XFAM field.
XFAM [8]byte `json:"xfam,omitempty"`
XFAM [8]byte `json:"xfam"`
}
// VersionAPIEntry is the request to get the version information of the specific version in the config api.

View File

@ -103,14 +103,14 @@ func (v *Validator) validateQuote(tdxQuote *tdx.QuoteV4) error {
if err := validate.TdxQuote(tdxQuote, &validate.Options{
HeaderOptions: validate.HeaderOptions{
MinimumQeSvn: v.cfg.QESVN,
MinimumPceSvn: v.cfg.PCESVN,
QeVendorID: v.cfg.QEVendorID,
MinimumQeSvn: v.cfg.QESVN.Value,
MinimumPceSvn: v.cfg.PCESVN.Value,
QeVendorID: v.cfg.QEVendorID.Value,
},
TdQuoteBodyOptions: validate.TdQuoteBodyOptions{
MinimumTeeTcbSvn: v.cfg.TEETCBSVN,
MinimumTeeTcbSvn: v.cfg.TEETCBSVN.Value,
MrSeam: v.cfg.MRSeam,
Xfam: v.cfg.XFAM,
Xfam: v.cfg.XFAM.Value,
},
}); err != nil {
return err

View File

@ -63,6 +63,7 @@ go_test(
"//internal/cloud/cloudprovider",
"//internal/config/instancetypes",
"//internal/constants",
"//internal/encoding",
"//internal/file",
"//internal/semver",
"//internal/versions",

View File

@ -9,47 +9,49 @@ package config
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"github.com/edgelesssys/constellation/v2/internal/encoding"
)
const placeholderVersionValue = 0
type versionValue interface {
encoding.HexBytes | uint8 | uint16
}
func placeholderVersionValue[T versionValue]() T {
var placeholder T
return placeholder
}
// NewLatestPlaceholderVersion returns the latest version with a placeholder version value.
func NewLatestPlaceholderVersion() AttestationVersion {
return AttestationVersion{
Value: placeholderVersionValue,
func NewLatestPlaceholderVersion[T versionValue]() AttestationVersion[T] {
return AttestationVersion[T]{
Value: placeholderVersionValue[T](),
WantLatest: true,
}
}
// AttestationVersion is a type that represents a version of a SNP.
type AttestationVersion struct {
Value uint8
// AttestationVersion holds version information.
type AttestationVersion[T versionValue] struct {
Value T
WantLatest bool
}
// MarshalYAML implements a custom marshaller to resolve "latest" values.
func (v AttestationVersion) MarshalYAML() (any, error) {
// MarshalYAML implements a custom marshaller to write "latest" as the type's value, if set.
func (v AttestationVersion[T]) MarshalYAML() (any, error) {
if v.WantLatest {
return "latest", nil
}
return v.Value, nil
}
// UnmarshalYAML implements a custom unmarshaller to resolve "atest" values.
func (v *AttestationVersion) UnmarshalYAML(unmarshal func(any) error) error {
var rawUnmarshal string
if err := unmarshal(&rawUnmarshal); err != nil {
return fmt.Errorf("raw unmarshal: %w", err)
}
return v.parseRawUnmarshal(rawUnmarshal)
// UnmarshalYAML implements a custom unmarshaller to resolve "latest" values.
func (v *AttestationVersion[T]) UnmarshalYAML(unmarshal func(any) error) error {
return v.unmarshal(unmarshal)
}
// MarshalJSON implements a custom marshaller to resolve "latest" values.
func (v AttestationVersion) MarshalJSON() ([]byte, error) {
// MarshalJSON implements a custom marshaller to write "latest" as the type's value, if set.
func (v AttestationVersion[T]) MarshalJSON() ([]byte, error) {
if v.WantLatest {
return json.Marshal("latest")
}
@ -57,39 +59,31 @@ func (v AttestationVersion) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON implements a custom unmarshaller to resolve "latest" values.
func (v *AttestationVersion) UnmarshalJSON(data []byte) (err error) {
// JSON has two distinct ways to represent numbers and strings.
// This means we cannot simply unmarshal to string, like with YAML.
// Unmarshalling to `any` causes Go to unmarshal numbers to float64.
// Therefore, try to unmarshal to string, and then to int, instead of using type assertions.
func (v *AttestationVersion[T]) UnmarshalJSON(data []byte) (err error) {
return v.unmarshal(func(a any) error {
return json.Unmarshal(data, a)
})
}
// unmarshal takes care of unmarshalling the value from YAML or JSON.
func (v *AttestationVersion[T]) unmarshal(unmarshal func(any) error) error {
// Start by trying to unmarshal to the distinct type
var distinctType T
if err := unmarshal(&distinctType); err == nil {
v.Value = distinctType
return nil
}
var unmarshalString string
if err := json.Unmarshal(data, &unmarshalString); err != nil {
var unmarshalInt int64
if err := json.Unmarshal(data, &unmarshalInt); err != nil {
return fmt.Errorf("unable to unmarshal to string or int: %w", err)
}
unmarshalString = strconv.FormatInt(unmarshalInt, 10)
if err := unmarshal(&unmarshalString); err != nil {
return fmt.Errorf("failed unmarshalling to %T or string: %w", distinctType, err)
}
return v.parseRawUnmarshal(unmarshalString)
}
func (v *AttestationVersion) parseRawUnmarshal(str string) error {
if strings.HasPrefix(str, "0") && len(str) != 1 {
return fmt.Errorf("no format with prefixed 0 (octal, hexadecimal) allowed: %s", str)
}
if strings.ToLower(str) == "latest" {
if strings.ToLower(unmarshalString) == "latest" {
v.WantLatest = true
v.Value = placeholderVersionValue
} else {
ui, err := strconv.ParseUint(str, 10, 8)
if err != nil {
return fmt.Errorf("invalid version value: %s", str)
}
if ui > math.MaxUint8 {
return fmt.Errorf("integer value is out ouf uint8 range: %d", ui)
}
v.Value = uint8(ui)
v.Value = placeholderVersionValue[T]()
return nil
}
return nil
return fmt.Errorf("failed unmarshalling to %T or string: invalid value: %s", distinctType, unmarshalString)
}

View File

@ -7,204 +7,307 @@ SPDX-License-Identifier: AGPL-3.0-only
package config
import (
"bytes"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/edgelesssys/constellation/v2/internal/encoding"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestVersionMarshalYAML(t *testing.T) {
tests := map[string]struct {
sut AttestationVersion
testCasesUint8 := map[string]struct {
sut AttestationVersion[uint8]
want string
}{
"isLatest resolves to latest": {
sut: AttestationVersion{
"version with latest writes latest": {
sut: AttestationVersion[uint8]{
Value: 1,
WantLatest: true,
},
want: "latest\n",
},
"value 5 resolves to 5": {
sut: AttestationVersion{
"value 5 writes 5": {
sut: AttestationVersion[uint8]{
Value: 5,
WantLatest: false,
},
want: "5\n",
},
}
for name, tc := range tests {
for name, tc := range testCasesUint8 {
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
bt, err := yaml.Marshal(tc.sut)
require.NoError(err)
require.Equal(tc.want, string(bt))
assert.NoError(err)
assert.Equal(tc.want, string(bt))
})
}
testCasesUint16 := map[string]struct {
sut AttestationVersion[uint16]
want string
}{
"version with latest writes latest": {
sut: AttestationVersion[uint16]{
Value: 1,
WantLatest: true,
},
want: "latest\n",
},
"value 5 writes 5": {
sut: AttestationVersion[uint16]{
Value: 5,
WantLatest: false,
},
want: "5\n",
},
}
for name, tc := range testCasesUint16 {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
bt, err := yaml.Marshal(tc.sut)
assert.NoError(err)
assert.Equal(tc.want, string(bt))
})
}
testCasesHexBytes := map[string]struct {
sut AttestationVersion[encoding.HexBytes]
want string
}{
"version with latest writes latest": {
sut: AttestationVersion[encoding.HexBytes]{
Value: encoding.HexBytes(bytes.Repeat([]byte("0"), 16)),
WantLatest: true,
},
want: "latest\n",
},
"value 5 writes 5": {
sut: AttestationVersion[encoding.HexBytes]{
Value: encoding.HexBytes(bytes.Repeat([]byte("A"), 16)),
WantLatest: false,
},
want: "\"41414141414141414141414141414141\"\n",
},
}
for name, tc := range testCasesHexBytes {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
bt, err := yaml.Marshal(tc.sut)
assert.NoError(err)
assert.Equal(tc.want, string(bt))
})
}
}
func TestVersionUnmarshalYAML(t *testing.T) {
tests := map[string]struct {
sut string
want AttestationVersion
wantErr bool
func TestVersionUnmarshal(t *testing.T) {
testCasesUint8 := map[string]struct {
yamlData string
jsonData string
want AttestationVersion[uint8]
wantErr bool
}{
"latest resolves to isLatest": {
sut: "latest",
want: AttestationVersion{
yamlData: "latest",
jsonData: "\"latest\"",
want: AttestationVersion[uint8]{
Value: 0,
WantLatest: true,
},
wantErr: false,
},
"1 resolves to value 1": {
sut: "1",
want: AttestationVersion{
yamlData: "1",
jsonData: "1",
want: AttestationVersion[uint8]{
Value: 1,
WantLatest: false,
},
wantErr: false,
},
"max uint8+1 errors": {
sut: "256",
wantErr: true,
yamlData: "256",
jsonData: "256",
wantErr: true,
},
"-1 errors": {
sut: "-1",
wantErr: true,
},
"2.6 errors": {
sut: "2.6",
wantErr: true,
},
"2.0 errors": {
sut: "2.0",
wantErr: true,
},
"hex format is invalid": {
sut: "0x10",
wantErr: true,
},
"octal format is invalid": {
sut: "010",
wantErr: true,
yamlData: "-1",
jsonData: "-1",
wantErr: true,
},
"0 resolves to value 0": {
sut: "0",
want: AttestationVersion{
yamlData: "0",
jsonData: "0",
want: AttestationVersion[uint8]{
Value: 0,
WantLatest: false,
},
},
"00 errors": {
sut: "00",
wantErr: true,
},
}
for name, tc := range tests {
for name, tc := range testCasesUint8 {
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
var sut AttestationVersion
err := yaml.Unmarshal([]byte(tc.sut), &sut)
if tc.wantErr {
require.Error(err)
return
{
var sut AttestationVersion[uint8]
err := yaml.Unmarshal([]byte(tc.yamlData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
{
var sut AttestationVersion[uint8]
err := json.Unmarshal([]byte(tc.jsonData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
require.NoError(err)
require.Equal(tc.want, sut)
})
}
}
func TestVersionUnmarshalJSON(t *testing.T) {
tests := map[string]struct {
sut string
want AttestationVersion
wantErr bool
testCasesUint16 := map[string]struct {
yamlData string
jsonData string
want AttestationVersion[uint16]
wantErr bool
}{
"latest resolves to isLatest": {
sut: `"latest"`,
want: AttestationVersion{
yamlData: "latest",
jsonData: "\"latest\"",
want: AttestationVersion[uint16]{
Value: 0,
WantLatest: true,
},
wantErr: false,
},
"1 resolves to value 1": {
sut: "1",
want: AttestationVersion{
yamlData: "1",
jsonData: "1",
want: AttestationVersion[uint16]{
Value: 1,
WantLatest: false,
},
wantErr: false,
},
"quoted number resolves to value": {
sut: `"1"`,
want: AttestationVersion{
Value: 1,
WantLatest: false,
},
},
"quoted float errors": {
sut: `"1.0"`,
wantErr: true,
},
"max uint8+1 errors": {
sut: "256",
wantErr: true,
"max uint16+1 errors": {
yamlData: "65536",
jsonData: "65536",
wantErr: true,
},
"-1 errors": {
sut: "-1",
wantErr: true,
},
"2.6 errors": {
sut: "2.6",
wantErr: true,
},
"2.0 errors": {
sut: "2.0",
wantErr: true,
},
"hex format is invalid": {
sut: "0x10",
wantErr: true,
},
"octal format is invalid": {
sut: "010",
wantErr: true,
yamlData: "-1",
jsonData: "-1",
wantErr: true,
},
"0 resolves to value 0": {
sut: "0",
want: AttestationVersion{
yamlData: "0",
jsonData: "0",
want: AttestationVersion[uint16]{
Value: 0,
WantLatest: false,
},
},
"quoted 0 resolves to value 0": {
sut: `"0"`,
want: AttestationVersion{
Value: 0,
WantLatest: false,
},
},
"00 errors": {
sut: "00",
wantErr: true,
},
}
for name, tc := range tests {
for name, tc := range testCasesUint16 {
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
var sut AttestationVersion
err := json.Unmarshal([]byte(tc.sut), &sut)
if tc.wantErr {
require.Error(err)
return
{
var sut AttestationVersion[uint16]
err := yaml.Unmarshal([]byte(tc.yamlData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
{
var sut AttestationVersion[uint16]
err := json.Unmarshal([]byte(tc.jsonData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
})
}
testCasesHexBytes := map[string]struct {
yamlData string
jsonData string
want AttestationVersion[encoding.HexBytes]
wantErr bool
}{
"latest resolves to isLatest": {
yamlData: "latest",
jsonData: "\"latest\"",
want: AttestationVersion[encoding.HexBytes]{
Value: encoding.HexBytes(nil),
WantLatest: true,
},
wantErr: false,
},
"hex string resolves to correctly": {
yamlData: "41414141414141414141414141414141",
jsonData: "\"41414141414141414141414141414141\"",
want: AttestationVersion[encoding.HexBytes]{
Value: encoding.HexBytes(bytes.Repeat([]byte("A"), 16)),
WantLatest: false,
},
wantErr: false,
},
"invalid hex string": {
yamlData: "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG",
jsonData: "\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"",
wantErr: true,
},
"non hex data": {
yamlData: "-15",
jsonData: "-15",
wantErr: true,
},
}
for name, tc := range testCasesHexBytes {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
{
var sut AttestationVersion[encoding.HexBytes]
err := yaml.Unmarshal([]byte(tc.yamlData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
{
var sut AttestationVersion[encoding.HexBytes]
err := json.Unmarshal([]byte(tc.jsonData), &sut)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, sut)
}
}
require.NoError(err)
require.Equal(tc.want, sut)
})
}
}

View File

@ -22,10 +22,10 @@ var _ svnResolveMarshaller = &AWSSEVSNP{}
func DefaultForAWSSEVSNP() *AWSSEVSNP {
return &AWSSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}),
BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: NewLatestPlaceholderVersion(),
BootloaderVersion: NewLatestPlaceholderVersion[uint8](),
TEEVersion: NewLatestPlaceholderVersion[uint8](),
SNPVersion: NewLatestPlaceholderVersion[uint8](),
MicrocodeVersion: NewLatestPlaceholderVersion[uint8](),
AMDRootKey: mustParsePEM(arkPEM),
}
}

View File

@ -28,10 +28,10 @@ var (
func DefaultForAzureSEVSNP() *AzureSEVSNP {
return &AzureSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureSEVSNP{}),
BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: NewLatestPlaceholderVersion(),
BootloaderVersion: NewLatestPlaceholderVersion[uint8](),
TEEVersion: NewLatestPlaceholderVersion[uint8](),
SNPVersion: NewLatestPlaceholderVersion[uint8](),
MicrocodeVersion: NewLatestPlaceholderVersion[uint8](),
FirmwareSignerConfig: SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.DefaultList(),
EnforcementPolicy: idkeydigest.MAAFallback,
@ -142,14 +142,14 @@ func DefaultForAzureTDX() *AzureTDX {
return &AzureTDX{
Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTDX{}),
// TODO(AB#3798): Enable latest versioning for Azure TDX
QESVN: 0,
PCESVN: 0,
TEETCBSVN: encoding.HexBytes{0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
QEVendorID: encoding.HexBytes{0x93, 0x9a, 0x72, 0x33, 0xf7, 0x9c, 0x4c, 0xa9, 0x94, 0x0a, 0x0d, 0xb3, 0x95, 0x7f, 0x06, 0x07},
QESVN: NewLatestPlaceholderVersion[uint16](),
PCESVN: NewLatestPlaceholderVersion[uint16](),
TEETCBSVN: NewLatestPlaceholderVersion[encoding.HexBytes](),
QEVendorID: NewLatestPlaceholderVersion[encoding.HexBytes](),
// Don't set a default for MRSEAM as it effectively prevents upgrading the SEAM module
// Quote verification still makes sure the module comes from Intel (through MRSIGNERSEAM), and is not of a lower version than expected
// MRSeam: nil,
XFAM: encoding.HexBytes{0xe7, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
XFAM: NewLatestPlaceholderVersion[encoding.HexBytes](),
IntelRootKey: mustParsePEM(tdxRootPEM),
}
@ -179,9 +179,43 @@ func (c AzureTDX) EqualTo(other AttestationCfg) (bool, error) {
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}
// FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them.
func (c *AzureTDX) FetchAndSetLatestVersionNumbers(ctx context.Context, fetcher attestationconfigapi.Fetcher) error {
// Only talk to the API if at least one version number is set to latest.
if !(c.PCESVN.WantLatest || c.QESVN.WantLatest || c.TEETCBSVN.WantLatest || c.QEVendorID.WantLatest || c.XFAM.WantLatest) {
return nil
}
versions, err := fetcher.FetchLatestVersion(ctx, variant.AzureTDX{})
if err != nil {
return fmt.Errorf("fetching latest TCB versions from configapi: %w", err)
}
// set values and keep WantLatest flag
if c.PCESVN.WantLatest {
c.PCESVN.Value = versions.PCESVN
}
if c.QESVN.WantLatest {
c.QESVN.Value = versions.QESVN
}
if c.TEETCBSVN.WantLatest {
c.TEETCBSVN.Value = versions.TEETCBSVN[:]
}
if c.QEVendorID.WantLatest {
c.QEVendorID.Value = versions.QEVendorID[:]
}
if c.XFAM.WantLatest {
c.XFAM.Value = versions.XFAM[:]
}
return nil
}
func (c *AzureTDX) getToMarshallLatestWithResolvedVersions() AttestationCfg {
cp := *c
// TODO: We probably want to support "latest" pseudo versioning for Azure TDX
// But we should decide on which claims can be reliably used for attestation first
cp.PCESVN.WantLatest = false
cp.QESVN.WantLatest = false
cp.TEETCBSVN.WantLatest = false
cp.QEVendorID.WantLatest = false
cp.XFAM.WantLatest = false
return &cp
}

View File

@ -468,18 +468,22 @@ func New(fileHandler file.Handler, name string, fetcher attestationconfigapi.Fet
return nil, err
}
// Replace "latest" placeholders for attestation version numbers with the actual latest version numbers from config API
if azure := c.Attestation.AzureSEVSNP; azure != nil {
if err := azure.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil {
return c, err
}
}
if azure := c.Attestation.AzureTDX; azure != nil {
if err := azure.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil {
return c, err
}
}
if aws := c.Attestation.AWSSEVSNP; aws != nil {
if err := aws.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil {
return c, err
}
}
if gcp := c.Attestation.GCPSEVSNP; gcp != nil {
if err := gcp.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil {
return c, err
@ -993,16 +997,16 @@ type GCPSEVSNP struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Lowest acceptable bootloader version.
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"`
// description: |
// Lowest acceptable TEE version.
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"`
// description: |
// Lowest acceptable SEV-SNP version.
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"`
// description: |
// Lowest acceptable microcode version.
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"`
// description: |
// AMD Root Key certificate used to verify the SEV-SNP certificate chain.
AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"`
@ -1080,16 +1084,16 @@ type AWSSEVSNP struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Lowest acceptable bootloader version.
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"`
// description: |
// Lowest acceptable TEE version.
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"`
// description: |
// Lowest acceptable SEV-SNP version.
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"`
// description: |
// Lowest acceptable microcode version.
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"`
// description: |
// AMD Root Key certificate used to verify the SEV-SNP certificate chain.
AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"`
@ -1112,16 +1116,16 @@ type AzureSEVSNP struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Lowest acceptable bootloader version.
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"`
// description: |
// Lowest acceptable TEE version.
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"`
// description: |
// Lowest acceptable SEV-SNP version.
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"`
// description: |
// Lowest acceptable microcode version.
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"`
// description: |
// Configuration for validating the firmware signature.
FirmwareSignerConfig SNPFirmwareSignerConfig `json:"firmwareSignerConfig" yaml:"firmwareSignerConfig"`
@ -1147,22 +1151,22 @@ type AzureTDX struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Minimum required QE security version number (SVN).
QESVN uint16 `json:"qeSVN" yaml:"qeSVN"`
QESVN AttestationVersion[uint16] `json:"qeSVN" yaml:"qeSVN"`
// description: |
// Minimum required PCE security version number (SVN).
PCESVN uint16 `json:"pceSVN" yaml:"pceSVN"`
PCESVN AttestationVersion[uint16] `json:"pceSVN" yaml:"pceSVN"`
// description: |
// Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN).
TEETCBSVN encoding.HexBytes `json:"teeTCBSVN" yaml:"teeTCBSVN"`
TEETCBSVN AttestationVersion[encoding.HexBytes] `json:"teeTCBSVN" yaml:"teeTCBSVN"`
// description: |
// Expected 16 byte hex-encoded QE_VENDOR_ID field.
QEVendorID encoding.HexBytes `json:"qeVendorID" yaml:"qeVendorID"`
QEVendorID AttestationVersion[encoding.HexBytes] `json:"qeVendorID" yaml:"qeVendorID"`
// description: |
// Expected 48 byte hex-encoded MR_SEAM value.
MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"`
// description: |
// Expected 8 byte hex-encoded XFAM field.
XFAM encoding.HexBytes `json:"xfam" yaml:"xfam"`
XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"`
// description: |
// Intel Root Key certificate used to verify the TDX certificate chain.
IntelRootKey Certificate `json:"intelRootKey" yaml:"intelRootKey"`

View File

@ -70,10 +70,10 @@ func TestGetAttestationConfigMarshalsNumericalVersion(t *testing.T) {
var mp map[string]interface{}
require.NoError(yaml.Unmarshal(bt, &mp))
assert := assert.New(t)
assert.Equal(placeholderVersionValue, mp["microcodeVersion"])
assert.Equal(placeholderVersionValue, mp["teeVersion"])
assert.Equal(placeholderVersionValue, mp["snpVersion"])
assert.Equal(placeholderVersionValue, mp["bootloaderVersion"])
assert.EqualValues(placeholderVersionValue[uint8](), mp["microcodeVersion"])
assert.EqualValues(placeholderVersionValue[uint8](), mp["teeVersion"])
assert.EqualValues(placeholderVersionValue[uint8](), mp["snpVersion"])
assert.EqualValues(placeholderVersionValue[uint8](), mp["bootloaderVersion"])
}
func TestNew(t *testing.T) {
@ -99,19 +99,19 @@ func TestNew(t *testing.T) {
wantResult: func() *Config {
conf := Default()
modifyConfigForAzureToPassValidate(conf)
conf.Attestation.AzureSEVSNP.MicrocodeVersion = AttestationVersion{
conf.Attestation.AzureSEVSNP.MicrocodeVersion = AttestationVersion[uint8]{
Value: testCfg.Microcode,
WantLatest: true,
}
conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion{
conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion[uint8]{
Value: 2,
WantLatest: false,
}
conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion{
conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion[uint8]{
Value: 1,
WantLatest: false,
}
conf.Attestation.AzureSEVSNP.SNPVersion = AttestationVersion{
conf.Attestation.AzureSEVSNP.SNPVersion = AttestationVersion[uint8]{
Value: testCfg.SNP,
WantLatest: true,
}

View File

@ -22,10 +22,10 @@ var _ svnResolveMarshaller = &GCPSEVSNP{}
func DefaultForGCPSEVSNP() *GCPSEVSNP {
return &GCPSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVSNP{}),
BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: NewLatestPlaceholderVersion(),
BootloaderVersion: NewLatestPlaceholderVersion[uint8](),
TEEVersion: NewLatestPlaceholderVersion[uint8](),
SNPVersion: NewLatestPlaceholderVersion[uint8](),
MicrocodeVersion: NewLatestPlaceholderVersion[uint8](),
AMDRootKey: mustParsePEM(arkPEM),
}
}

View File

@ -415,19 +415,19 @@ func V3ToV4(path string, fileHandler file.Handler) error {
case cfgV3.Attestation.AzureSEVSNP != nil:
cfgV4.Attestation.AzureSEVSNP = &config.AzureSEVSNP{
Measurements: cfgV3.Attestation.AzureSEVSNP.Measurements,
BootloaderVersion: config.AttestationVersion{
BootloaderVersion: config.AttestationVersion[uint8]{
Value: cfgV3.Attestation.AzureSEVSNP.BootloaderVersion.Value,
WantLatest: cfgV3.Attestation.AzureSEVSNP.BootloaderVersion.WantLatest,
},
TEEVersion: config.AttestationVersion{
TEEVersion: config.AttestationVersion[uint8]{
Value: cfgV3.Attestation.AzureSEVSNP.TEEVersion.Value,
WantLatest: cfgV3.Attestation.AzureSEVSNP.TEEVersion.WantLatest,
},
SNPVersion: config.AttestationVersion{
SNPVersion: config.AttestationVersion[uint8]{
Value: cfgV3.Attestation.AzureSEVSNP.SNPVersion.Value,
WantLatest: cfgV3.Attestation.AzureSEVSNP.SNPVersion.WantLatest,
},
MicrocodeVersion: config.AttestationVersion{
MicrocodeVersion: config.AttestationVersion[uint8]{
Value: cfgV3.Attestation.AzureSEVSNP.MicrocodeVersion.Value,
WantLatest: cfgV3.Attestation.AzureSEVSNP.MicrocodeVersion.WantLatest,
},

View File

@ -32,6 +32,7 @@ go_library(
"//internal/constellation/helm",
"//internal/constellation/kubecmd",
"//internal/constellation/state",
"//internal/encoding",
"//internal/file",
"//internal/grpc/dialer",
"//internal/imagefetcher",

View File

@ -162,17 +162,18 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq
insecureFetch := data.Insecure.ValueBool()
snpVersions := attestationconfigapi.VersionAPIEntry{}
if attestationVariant.Equal(variant.AzureSEVSNP{}) ||
attestationVariant.Equal(variant.AWSSEVSNP{}) ||
latestVersions := attestationconfigapi.VersionAPIEntry{}
if attestationVariant.Equal(variant.AWSSEVSNP{}) ||
attestationVariant.Equal(variant.AzureSEVSNP{}) ||
attestationVariant.Equal(variant.AzureTDX{}) ||
attestationVariant.Equal(variant.GCPSEVSNP{}) {
snpVersions, err = d.fetcher.FetchLatestVersion(ctx, attestationVariant)
latestVersions, err = d.fetcher.FetchLatestVersion(ctx, attestationVariant)
if err != nil {
resp.Diagnostics.AddError("Fetching SNP Version numbers", err.Error())
return
}
}
tfAttestation, err := convertToTfAttestation(attestationVariant, snpVersions)
tfAttestation, err := convertToTfAttestation(attestationVariant, latestVersions)
if err != nil {
resp.Diagnostics.AddError("Converting attestation", err.Error())
}

View File

@ -17,6 +17,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/encoding"
)
// naming schema:
@ -110,12 +111,12 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation
attestationConfig = &config.AzureTDX{
Measurements: c11nMeasurements,
QESVN: tfAttestation.TDX.QESVN,
PCESVN: tfAttestation.TDX.PCESVN,
TEETCBSVN: teeTCBSVN,
QEVendorID: qeVendorID,
QESVN: newVersion(tfAttestation.TDX.QESVN),
PCESVN: newVersion(tfAttestation.TDX.PCESVN),
TEETCBSVN: newVersion(encoding.HexBytes(teeTCBSVN)),
QEVendorID: newVersion(encoding.HexBytes(qeVendorID)),
MRSeam: mrSeam,
XFAM: xfam,
XFAM: newVersion(encoding.HexBytes(xfam)),
IntelRootKey: rootKey,
}
case variant.GCPSEVES{}:
@ -137,13 +138,9 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation
}
// convertToTfAttestationCfg converts the constellation attestation config to the related terraform structs.
func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) {
func convertToTfAttestation(attVar variant.Variant, latestVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) {
tfAttestation = attestationAttribute{
Variant: attVar.String(),
BootloaderVersion: snpVersions.Bootloader,
TEEVersion: snpVersions.TEE,
SNPVersion: snpVersions.SNP,
MicrocodeVersion: snpVersions.Microcode,
Variant: attVar.String(),
}
switch attVar {
@ -153,6 +150,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi
return tfAttestation, err
}
tfAttestation.AMDRootKey = certStr
tfAttestation.BootloaderVersion = latestVersions.Bootloader
tfAttestation.TEEVersion = latestVersions.TEE
tfAttestation.SNPVersion = latestVersions.SNP
tfAttestation.MicrocodeVersion = latestVersions.Microcode
case variant.GCPSEVSNP{}:
certStr, err := certAsString(config.DefaultForGCPSEVSNP().AMDRootKey)
@ -160,6 +161,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi
return tfAttestation, err
}
tfAttestation.AMDRootKey = certStr
tfAttestation.BootloaderVersion = latestVersions.Bootloader
tfAttestation.TEEVersion = latestVersions.TEE
tfAttestation.SNPVersion = latestVersions.SNP
tfAttestation.MicrocodeVersion = latestVersions.Microcode
case variant.AzureSEVSNP{}:
certStr, err := certAsString(config.DefaultForAzureSEVSNP().AMDRootKey)
@ -167,6 +172,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi
return tfAttestation, err
}
tfAttestation.AMDRootKey = certStr
tfAttestation.BootloaderVersion = latestVersions.Bootloader
tfAttestation.TEEVersion = latestVersions.TEE
tfAttestation.SNPVersion = latestVersions.SNP
tfAttestation.MicrocodeVersion = latestVersions.Microcode
firmwareCfg := config.DefaultForAzureSEVSNP().FirmwareSignerConfig
tfFirmwareCfg, err := convertToTfFirmwareCfg(firmwareCfg)
@ -174,24 +183,19 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi
return tfAttestation, err
}
tfAttestation.AzureSNPFirmwareSignerConfig = tfFirmwareCfg
case variant.AzureTDX{}:
tdxCfg := config.DefaultForAzureTDX()
certStr, err := certAsString(tdxCfg.IntelRootKey)
certStr, err := certAsString(config.DefaultForAzureTDX().IntelRootKey)
if err != nil {
return tfAttestation, err
}
tfAttestation.TDX.IntelRootKey = certStr
tfAttestation.TDX.PCESVN = latestVersions.PCESVN
tfAttestation.TDX.QESVN = latestVersions.QESVN
tfAttestation.TDX.TEETCBSVN = hex.EncodeToString(latestVersions.TEETCBSVN[:])
tfAttestation.TDX.QEVendorID = hex.EncodeToString(latestVersions.QEVendorID[:])
tfAttestation.TDX.XFAM = hex.EncodeToString(latestVersions.XFAM[:])
tfTdxCfg := tdxConfigAttribute{
IntelRootKey: certStr,
// TODO(AB#3798): Load these values dynamically from our attestation API
QESVN: tdxCfg.QESVN,
PCESVN: tdxCfg.PCESVN,
TEETCBSVN: hex.EncodeToString(tdxCfg.TEETCBSVN),
QEVendorID: hex.EncodeToString(tdxCfg.QEVendorID),
MRSeam: hex.EncodeToString(tdxCfg.MRSeam),
XFAM: hex.EncodeToString(tdxCfg.XFAM),
}
tfAttestation.TDX = tfTdxCfg
case variant.GCPSEVES{}, variant.QEMUVTPM{}:
// no additional fields
default:
@ -251,8 +255,8 @@ func convertToTfMeasurements(m measurements.M) map[string]measurementAttribute {
return tfMeasurements
}
func newVersion(v uint8) config.AttestationVersion {
return config.AttestationVersion{
func newVersion[T uint8 | uint16 | encoding.HexBytes](v T) config.AttestationVersion[T] {
return config.AttestationVersion[T]{
Value: v,
}
}