attestation: add option for MAA fallback to verify azure's snp-sev id key digest (#1257)

* Convert enforceIDKeyDigest setting to enum

* Use MAA fallback in Azure SNP attestation

* Only create MAA provider if MAA fallback is enabled

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
Co-authored-by: Thomas Tendyck <tt@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-03-21 12:46:49 +01:00 committed by GitHub
parent 9a9688583d
commit 5a0234b3f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1073 additions and 542 deletions

View file

@ -3,7 +3,10 @@ load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "idkeydigest",
srcs = ["idkeydigest.go"],
srcs = [
"enforceidkeydigest_string.go",
"idkeydigest.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest",
visibility = ["//:__subpackages__"],
deps = ["//internal/cloud/cloudprovider"],

View file

@ -0,0 +1,26 @@
// Code generated by "stringer -type=EnforceIDKeyDigest"; DO NOT EDIT.
package idkeydigest
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Unknown-0]
_ = x[StrictChecking-1]
_ = x[MAAFallback-2]
_ = x[WarnOnly-3]
}
const _EnforceIDKeyDigest_name = "UnknownStrictCheckingMAAFallbackWarnOnly"
var _EnforceIDKeyDigest_index = [...]uint8{0, 7, 21, 32, 40}
func (i EnforceIDKeyDigest) String() string {
if i >= EnforceIDKeyDigest(len(_EnforceIDKeyDigest_index)-1) {
return "EnforceIDKeyDigest(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _EnforceIDKeyDigest_name[_EnforceIDKeyDigest_index[i]:_EnforceIDKeyDigest_index[i+1]]
}

View file

@ -4,6 +4,8 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package idkeydigest contains policies and type definitions
// for checking the ID Key Digest value in SEV-SNP attestation.
package idkeydigest
import (
@ -11,10 +13,105 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
)
// Config contains the configuration for ID Key Digest validation.
type Config struct {
IDKeyDigests IDKeyDigests `json:"idKeyDigests"`
EnforcementPolicy EnforceIDKeyDigest `json:"enforcementPolicy"`
MAAURL string `json:"maaURL,omitempty"`
}
//go:generate stringer -type=EnforceIDKeyDigest
// EnforceIDKeyDigest defines the behavior of the validator when the ID key digest is not found in the expected list.
type EnforceIDKeyDigest uint32
// TODO: Decide on final value naming.
const (
// Unknown is reserved for invalid configurations.
Unknown EnforceIDKeyDigest = iota
// StrictChecking will return an error if the ID key digest is not found in the expected list.
StrictChecking
// MAAFallback attempts to verify the attestation using Microsoft Azure Attestation (MAA),
// if the ID key digest is not found in the expected list.
MAAFallback
// WarnOnly logs a warning if the ID key digest is not found in the expected list.
// No error is returned.
WarnOnly
)
// UnmarshalJSON implements the json.Unmarshaler interface.
func (e *EnforceIDKeyDigest) UnmarshalJSON(b []byte) error {
return e.unmarshal(func(val any) error {
return json.Unmarshal(b, val)
})
}
// MarshalJSON implements the json.Marshaler interface.
func (e EnforceIDKeyDigest) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (e *EnforceIDKeyDigest) UnmarshalYAML(unmarshal func(any) error) error {
return e.unmarshal(unmarshal)
}
// MarshalYAML implements the yaml.Marshaler interface.
func (e EnforceIDKeyDigest) MarshalYAML() (any, error) {
return e.String(), nil
}
func (e *EnforceIDKeyDigest) unmarshal(unmarshalFunc func(any) error) error {
// Check for legacy format: EnforceIDKeyDigest might be a boolean.
// If set to true, the value will be set to StrictChecking.
// If set to false, the value will be set to WarnOnly.
var legacyEnforce bool
legacyErr := unmarshalFunc(&legacyEnforce)
if legacyErr == nil {
if legacyEnforce {
*e = StrictChecking
} else {
*e = WarnOnly
}
return nil
}
var enforce string
if err := unmarshalFunc(&enforce); err != nil {
return errors.Join(
err,
fmt.Errorf("trying legacy format: %w", legacyErr),
)
}
*e = EnforcePolicyFromString(enforce)
if *e == Unknown {
return fmt.Errorf("unknown EnforceIDKeyDigest value: %q", enforce)
}
return nil
}
// EnforcePolicyFromString returns EnforceIDKeyDigest from string.
func EnforcePolicyFromString(s string) EnforceIDKeyDigest {
s = strings.ToLower(s)
switch s {
case "strictchecking":
return StrictChecking
case "maafallback":
return MAAFallback
case "warnonly":
return WarnOnly
default:
return Unknown
}
}
// IDKeyDigests is a list of trusted digest values for the ID key.
type IDKeyDigests [][]byte

View file

@ -117,3 +117,126 @@ func TestUnmarshal(t *testing.T) {
})
}
}
func TestEnforceIDKeyDigestMarshal(t *testing.T) {
testCases := map[string]struct {
input EnforceIDKeyDigest
wantJSON string
wantYAML string
}{
"strict": {
input: StrictChecking,
wantJSON: `"StrictChecking"`,
wantYAML: "StrictChecking",
},
"maaFallback": {
input: MAAFallback,
wantJSON: `"MAAFallback"`,
wantYAML: "MAAFallback",
},
"warnOnly": {
input: WarnOnly,
wantJSON: `"WarnOnly"`,
wantYAML: "WarnOnly",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
{
// YAML
yaml, err := yaml.Marshal(tc.input)
require.NoError(err)
assert.YAMLEq(tc.wantYAML, string(yaml))
}
{
// JSON
json, err := json.Marshal(tc.input)
require.NoError(err)
assert.JSONEq(tc.wantJSON, string(json))
}
})
}
}
func TestEnforceIDKeyDigestUnmarshal(t *testing.T) {
testCases := map[string]struct {
inputJSON string
inputYAML string
want EnforceIDKeyDigest
wantErr bool
}{
"strict": {
inputJSON: `"StrictChecking"`,
inputYAML: "StrictChecking",
want: StrictChecking,
},
"maaFallback": {
inputJSON: `"MAAFallback"`,
inputYAML: "MAAFallback",
want: MAAFallback,
},
"warnOnly": {
inputJSON: `"WarnOnly"`,
inputYAML: "WarnOnly",
want: WarnOnly,
},
"legacyTrue": {
inputJSON: `true`,
inputYAML: "true",
want: StrictChecking,
},
"legacyFalse": {
inputJSON: `false`,
inputYAML: "false",
want: WarnOnly,
},
"invalid": {
inputJSON: `"invalid"`,
inputYAML: "invalid",
wantErr: true,
},
"invalidType": {
inputJSON: `{"object": "invalid"}`,
inputYAML: "object: invalid",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
{
// YAML
var got EnforceIDKeyDigest
err := yaml.Unmarshal([]byte(tc.inputYAML), &got)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.want, got)
}
{
// JSON
var got EnforceIDKeyDigest
err := json.Unmarshal([]byte(tc.inputJSON), &got)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}