mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
internal: use config to create attestation validators (#1561)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
2b962598bf
commit
ec01c57661
@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
@ -26,7 +25,7 @@ import (
|
||||
type Validator struct {
|
||||
attestationVariant variant.Variant
|
||||
pcrs measurements.M
|
||||
idKeyConfig idkeydigest.Config
|
||||
idKeyConfig config.SNPFirmwareSignerConfig
|
||||
validator atls.Validator
|
||||
log debugLog
|
||||
}
|
||||
@ -45,10 +44,10 @@ func NewValidator(conf *config.Config, maaURL string, log debugLog) (*Validator,
|
||||
}
|
||||
|
||||
if v.attestationVariant.Equal(variant.AzureSEVSNP{}) {
|
||||
v.idKeyConfig = idkeydigest.Config{
|
||||
IDKeyDigests: conf.Provider.Azure.IDKeyDigest,
|
||||
EnforcementPolicy: conf.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
v.idKeyConfig = config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: conf.Provider.Azure.IDKeyDigest,
|
||||
EnforcementPolicy: conf.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +120,8 @@ func TestNewValidator(t *testing.T) {
|
||||
Provider: config.ProviderConfig{
|
||||
Azure: &config.AzureConfig{
|
||||
Measurements: testPCRs,
|
||||
IDKeyDigest: idkeydigest.IDKeyDigests{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
|
||||
EnforceIDKeyDigest: idkeydigest.StrictChecking,
|
||||
IDKeyDigest: idkeydigest.List{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
|
||||
EnforceIDKeyDigest: idkeydigest.Equal,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -174,26 +174,31 @@ func TestValidatorV(t *testing.T) {
|
||||
"gcp": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: gcp.NewValidator(newTestPCRs(), nil),
|
||||
wantVs: gcp.NewValidator(config.GCPSEVES{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
"azure cvm": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: snp.NewValidator(
|
||||
newTestPCRs(),
|
||||
idkeydigest.Config{IDKeyDigests: idkeydigest.IDKeyDigests{}, EnforcementPolicy: idkeydigest.WarnOnly},
|
||||
config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
FirmwareSignerConfig: config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
),
|
||||
},
|
||||
"azure trusted launch": {
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: trustedlaunch.NewValidator(newTestPCRs(), nil),
|
||||
wantVs: trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
"qemu": {
|
||||
variant: variant.QEMUVTPM{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: qemu.NewValidator(newTestPCRs(), nil),
|
||||
wantVs: qemu.NewValidator(config.QEMUVTPM{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
|
||||
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
|
||||
cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
|
||||
printedAWarning = true
|
||||
if conf.IDKeyDigestPolicy() == idkeydigest.StrictChecking || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
|
||||
if conf.IDKeyDigestPolicy() == idkeydigest.Equal || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
|
||||
cmd.PrintErrln("Your config asks for validating the idkeydigest. This is only available on Confidential VMs. It will not be enforced.")
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
"idKeyConfig": {
|
||||
"description": "Configuration for validating the ID Key Digest of the SEV-SNP attestation.",
|
||||
"type": "string",
|
||||
"examples": ["{'EnforcementPolicy': 'MAAFallback', 'MAAURL': 'https://192.0.2.1:8080/maa', 'IDKeyDigests': ['57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696', '0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3'}"]
|
||||
"examples": ["{'enforcementPolicy': 'MAAFallback', 'maaURL': 'https://192.0.2.1:8080/maa', 'acceptedKeyDigests': ['57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696', '0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3'}"]
|
||||
},
|
||||
"image": {
|
||||
"description": "Container image to use for the spawned pods.",
|
||||
|
@ -380,16 +380,16 @@ func setAttestationVariant(values map[string]any, variant string) error {
|
||||
|
||||
// TODO: v2.8: remove. This function is only temporarily needed as a migration from 2.6 to 2.7.
|
||||
// setIdkeyConfig sets the idkeyconfig value on the join-service value maps.
|
||||
func setIdkeyConfig(values map[string]any, config *config.Config, maaURL string) error {
|
||||
func setIdkeyConfig(values map[string]any, cfg *config.Config, maaURL string) error {
|
||||
joinServiceVals, ok := values["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
|
||||
idKeyCfg := idkeydigest.Config{
|
||||
IDKeyDigests: config.IDKeyDigests(),
|
||||
EnforcementPolicy: config.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
idKeyCfg := config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: cfg.IDKeyDigests(),
|
||||
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
marshalledCfg, err := json.Marshal(idKeyCfg)
|
||||
if err != nil {
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
@ -466,7 +465,7 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
|
||||
// extendConstellationServicesValues extends the given values map by some values depending on user input.
|
||||
// Values set inside this function are only applied during init, not during upgrade.
|
||||
func extendConstellationServicesValues(
|
||||
in map[string]any, config *config.Config, masterSecret, salt []byte, maaURL string,
|
||||
in map[string]any, cfg *config.Config, masterSecret, salt []byte, maaURL string,
|
||||
) error {
|
||||
keyServiceValues, ok := in["key-service"].(map[string]any)
|
||||
if !ok {
|
||||
@ -479,11 +478,11 @@ func extendConstellationServicesValues(
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
joinServiceVals["attestationVariant"] = config.AttestationVariant
|
||||
joinServiceVals["attestationVariant"] = cfg.AttestationVariant
|
||||
|
||||
// measurements are updated separately during upgrade,
|
||||
// so we only set them in Helm during init.
|
||||
measurementsJSON, err := json.Marshal(config.GetMeasurements())
|
||||
measurementsJSON, err := json.Marshal(cfg.GetMeasurements())
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling measurements: %w", err)
|
||||
}
|
||||
@ -493,9 +492,9 @@ func extendConstellationServicesValues(
|
||||
if !ok {
|
||||
return errors.New("invalid verification-service values")
|
||||
}
|
||||
verifyServiceVals["attestationVariant"] = config.AttestationVariant
|
||||
verifyServiceVals["attestationVariant"] = cfg.AttestationVariant
|
||||
|
||||
csp := config.GetProvider()
|
||||
csp := cfg.GetProvider()
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
joinServiceVals, ok := in["join-service"].(map[string]any)
|
||||
@ -503,10 +502,10 @@ func extendConstellationServicesValues(
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
|
||||
idKeyCfg := idkeydigest.Config{
|
||||
IDKeyDigests: config.IDKeyDigests(),
|
||||
EnforcementPolicy: config.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
idKeyCfg := config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: cfg.IDKeyDigests(),
|
||||
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
marshalledCfg, err := json.Marshal(idKeyCfg)
|
||||
if err != nil {
|
||||
@ -515,12 +514,12 @@ func extendConstellationServicesValues(
|
||||
joinServiceVals["idKeyConfig"] = string(marshalledCfg)
|
||||
|
||||
in["azure"] = map[string]any{
|
||||
"deployCSIDriver": config.DeployCSIDriver(),
|
||||
"deployCSIDriver": cfg.DeployCSIDriver(),
|
||||
}
|
||||
|
||||
case cloudprovider.GCP:
|
||||
in["gcp"] = map[string]any{
|
||||
"deployCSIDriver": config.DeployCSIDriver(),
|
||||
"deployCSIDriver": cfg.DeployCSIDriver(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ func TestConstellationServices(t *testing.T) {
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Provider: config.ProviderConfig{Azure: &config.AzureConfig{
|
||||
DeployCSIDriver: toPtr(true),
|
||||
EnforceIDKeyDigest: idkeydigest.StrictChecking,
|
||||
EnforceIDKeyDigest: idkeydigest.Equal,
|
||||
IDKeyDigest: [][]byte{
|
||||
{0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad},
|
||||
{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa},
|
||||
|
@ -5,6 +5,6 @@ metadata:
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
idKeyConfig: "{\"idKeyDigests\":[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"StrictChecking\",\"maaURL\":\"https://192.0.2.1:8080/maa\"}"
|
||||
idKeyConfig: "{\"acceptedKeyDigests\":[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"Equal\",\"maaURL\":\"https://192.0.2.1:8080/maa\"}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -11,8 +11,8 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/aws",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||
"@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds",
|
||||
|
@ -15,8 +15,8 @@ import (
|
||||
awsConfig "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
@ -30,10 +30,10 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// NewValidator create a new Validator structure and returns it.
|
||||
func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
|
||||
func NewValidator(cfg config.AWSNitroTPM, log vtpm.AttestationLogger) *Validator {
|
||||
v := &Validator{}
|
||||
v.Validator = vtpm.NewValidator(
|
||||
pcrs,
|
||||
cfg.Measurements,
|
||||
getTrustedKey,
|
||||
v.tpmEnabled,
|
||||
log,
|
||||
|
@ -15,9 +15,9 @@ go_library(
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/cloud/azure",
|
||||
"//internal/config",
|
||||
"//internal/crypto",
|
||||
"//internal/variant",
|
||||
"@com_github_edgelesssys_go_azguestattestation//maa",
|
||||
@ -44,6 +44,7 @@ go_test(
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/simulator",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/logger",
|
||||
"@com_github_edgelesssys_go_azguestattestation//maa",
|
||||
"@com_github_google_go_tpm//tpm2",
|
||||
|
@ -9,8 +9,6 @@ package snp
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
)
|
||||
|
||||
type signatureError struct {
|
||||
@ -51,7 +49,7 @@ func (e *vcekError) Error() string {
|
||||
|
||||
type idKeyError struct {
|
||||
encounteredValue []byte
|
||||
expectedValues idkeydigest.IDKeyDigests
|
||||
expectedValues [][]byte
|
||||
}
|
||||
|
||||
func (e *idKeyError) Unwrap() error {
|
||||
|
@ -21,23 +21,14 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
internalCrypto "github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
)
|
||||
|
||||
const (
|
||||
// AMD root key. Received from the AMD Key Distribution System API (KDS).
|
||||
arkPEM = "-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n"
|
||||
bootloaderVersion = 2
|
||||
teeVersion = 0
|
||||
snpVersion = 6
|
||||
microcodeVersion = 93
|
||||
)
|
||||
|
||||
// Validator for Azure confidential VM attestation.
|
||||
type Validator struct {
|
||||
variant.AzureSEVSNP
|
||||
@ -45,28 +36,24 @@ type Validator struct {
|
||||
hclValidator hclAkValidator
|
||||
maa maaValidator
|
||||
|
||||
idKeyDigests idkeydigest.IDKeyDigests
|
||||
enforceIDKeyDigest idkeydigest.EnforceIDKeyDigest
|
||||
maaURL string
|
||||
config config.AzureSEVSNP
|
||||
|
||||
log vtpm.AttestationLogger
|
||||
}
|
||||
|
||||
// NewValidator initializes a new Azure validator with the provided PCR values.
|
||||
func NewValidator(pcrs measurements.M, idKeyConf idkeydigest.Config, log vtpm.AttestationLogger) *Validator {
|
||||
func NewValidator(cfg config.AzureSEVSNP, log vtpm.AttestationLogger) *Validator {
|
||||
if log == nil {
|
||||
log = nopAttestationLogger{}
|
||||
}
|
||||
v := &Validator{
|
||||
hclValidator: &azureInstanceInfo{},
|
||||
maa: newMAAClient(),
|
||||
idKeyDigests: idKeyConf.IDKeyDigests,
|
||||
enforceIDKeyDigest: idKeyConf.EnforcementPolicy,
|
||||
maaURL: idKeyConf.MAAURL,
|
||||
log: log,
|
||||
hclValidator: &azureInstanceInfo{},
|
||||
maa: newMAAClient(),
|
||||
config: cfg,
|
||||
log: log,
|
||||
}
|
||||
v.Validator = vtpm.NewValidator(
|
||||
pcrs,
|
||||
cfg.Measurements,
|
||||
v.getTrustedKey,
|
||||
validateCVM,
|
||||
log,
|
||||
@ -107,7 +94,7 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
||||
return nil, fmt.Errorf("parsing attestation report: %w", err)
|
||||
}
|
||||
|
||||
vcek, err := validateVCEK(instanceInfo.Vcek, instanceInfo.CertChain)
|
||||
vcek, err := v.validateVCEK(instanceInfo.Vcek, instanceInfo.CertChain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating VCEK: %w", err)
|
||||
}
|
||||
@ -130,24 +117,19 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
||||
|
||||
// validateVCEK takes the PEM-encoded X509 certificate VCEK, ASK and ARK and verifies the integrity of the chain.
|
||||
// ARK (hardcoded) validates ASK (cloud metadata API) validates VCEK (cloud metadata API).
|
||||
func validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) {
|
||||
func (v *Validator) validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) {
|
||||
vcek, err := internalCrypto.PemToX509Cert(vcekRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading vcek: %w", err)
|
||||
}
|
||||
|
||||
ark, err := internalCrypto.PemToX509Cert([]byte(arkPEM))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading arkPEM: %w", err)
|
||||
}
|
||||
|
||||
// certChain includes two PEM encoded certs. The ASK and the ARK, in that order.
|
||||
ask, err := internalCrypto.PemToX509Cert(certChain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading askPEM: %w", err)
|
||||
}
|
||||
|
||||
if err = ask.CheckSignatureFrom(ark); err != nil {
|
||||
if err = ask.CheckSignatureFrom((*x509.Certificate)(&v.config.AMDRootKey)); err != nil {
|
||||
return nil, &askError{err}
|
||||
}
|
||||
|
||||
@ -165,7 +147,7 @@ func (v *Validator) validateSNPReport(
|
||||
return errDebugEnabled
|
||||
}
|
||||
|
||||
if !report.CommittedTCB.isVersion(bootloaderVersion, teeVersion, snpVersion, microcodeVersion) {
|
||||
if !report.CommittedTCB.isVersion(v.config.BootloaderVersion, v.config.TEEVersion, v.config.SNPVersion, v.config.MicrocodeVersion) {
|
||||
return &versionError{"COMMITTED_TCB", report.CommittedTCB}
|
||||
}
|
||||
if report.LaunchTCB != report.CommittedTCB {
|
||||
@ -205,7 +187,7 @@ func (v *Validator) validateSNPReport(
|
||||
}
|
||||
|
||||
hasExpectedIDKeyDigest := false
|
||||
for _, digest := range v.idKeyDigests {
|
||||
for _, digest := range v.config.FirmwareSignerConfig.AcceptedKeyDigests {
|
||||
if bytes.Equal(digest, report.IDKeyDigest[:]) {
|
||||
hasExpectedIDKeyDigest = true
|
||||
break
|
||||
@ -213,14 +195,22 @@ func (v *Validator) validateSNPReport(
|
||||
}
|
||||
|
||||
if !hasExpectedIDKeyDigest {
|
||||
switch v.enforceIDKeyDigest {
|
||||
switch v.config.FirmwareSignerConfig.EnforcementPolicy {
|
||||
case idkeydigest.MAAFallback:
|
||||
v.log.Infof("configured idkeydigests %x don't contain reported idkeydigest %x, falling back to MAA validation", v.idKeyDigests, report.IDKeyDigest[:])
|
||||
return v.maa.validateToken(ctx, v.maaURL, maaToken, extraData)
|
||||
v.log.Infof(
|
||||
"configured idkeydigests %x don't contain reported idkeydigest %x, falling back to MAA validation",
|
||||
v.config.FirmwareSignerConfig.AcceptedKeyDigests,
|
||||
report.IDKeyDigest[:],
|
||||
)
|
||||
return v.maa.validateToken(ctx, v.config.FirmwareSignerConfig.MAAURL, maaToken, extraData)
|
||||
case idkeydigest.WarnOnly:
|
||||
v.log.Warnf("configured idkeydigests %x don't contain reported idkeydigest %x", v.idKeyDigests, report.IDKeyDigest[:])
|
||||
v.log.Warnf(
|
||||
"configured idkeydigests %x don't contain reported idkeydigest %x",
|
||||
v.config.FirmwareSignerConfig.AcceptedKeyDigests,
|
||||
report.IDKeyDigest[:],
|
||||
)
|
||||
default:
|
||||
return &idKeyError{report.IDKeyDigest[:], v.idKeyDigests}
|
||||
return &idKeyError{report.IDKeyDigest[:], v.config.FirmwareSignerConfig.AcceptedKeyDigests}
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -11,8 +11,8 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/crypto",
|
||||
"//internal/variant",
|
||||
"@com_github_google_go_tpm//tpm2",
|
||||
@ -34,6 +34,7 @@ go_test(
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/simulator",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/crypto",
|
||||
"//internal/logger",
|
||||
"@com_github_google_go_tpm//tpm2",
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
@ -200,7 +201,7 @@ func TestGetAttestationCert(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := NewValidator(measurements.M{}, nil)
|
||||
validator := NewValidator(config.AzureTrustedLaunch{Measurements: measurements.M{}}, nil)
|
||||
cert, err := x509.ParseCertificate(rootCert.Raw)
|
||||
require.NoError(err)
|
||||
roots := x509.NewCertPool()
|
||||
|
@ -15,8 +15,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
certutil "github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
@ -35,12 +35,12 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// NewValidator initializes a new Azure validator with the provided PCR values.
|
||||
func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
|
||||
func NewValidator(cfg config.AzureTrustedLaunch, log vtpm.AttestationLogger) *Validator {
|
||||
rootPool := x509.NewCertPool()
|
||||
rootPool.AddCert(ameRoot)
|
||||
v := &Validator{roots: rootPool}
|
||||
v.Validator = vtpm.NewValidator(
|
||||
pcrs,
|
||||
cfg.Measurements,
|
||||
v.verifyAttestationKey,
|
||||
validateVM,
|
||||
log,
|
||||
|
@ -12,10 +12,10 @@ go_library(
|
||||
"//internal/attestation/azure/snp",
|
||||
"//internal/attestation/azure/trustedlaunch",
|
||||
"//internal/attestation/gcp",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/qemu",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
],
|
||||
)
|
||||
@ -25,7 +25,7 @@ go_test(
|
||||
srcs = ["choose_test.go"],
|
||||
embed = [":choose"],
|
||||
deps = [
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
|
@ -14,10 +14,10 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
@ -43,19 +43,22 @@ func Issuer(attestationVariant variant.Variant, log vtpm.AttestationLogger) (atl
|
||||
|
||||
// Validator returns the validator for the given variant.
|
||||
func Validator(
|
||||
attestationVariant variant.Variant, measurements measurements.M, idKeyCfg idkeydigest.Config, log vtpm.AttestationLogger,
|
||||
attestationVariant variant.Variant, measurements measurements.M, idKeyCfg config.SNPFirmwareSignerConfig, log vtpm.AttestationLogger,
|
||||
) (atls.Validator, error) {
|
||||
switch attestationVariant {
|
||||
case variant.AWSNitroTPM{}:
|
||||
return aws.NewValidator(measurements, log), nil
|
||||
return aws.NewValidator(config.AWSNitroTPM{Measurements: measurements}, log), nil
|
||||
case variant.AzureTrustedLaunch{}:
|
||||
return trustedlaunch.NewValidator(measurements, log), nil
|
||||
return trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: measurements}, log), nil
|
||||
case variant.AzureSEVSNP{}:
|
||||
return snp.NewValidator(measurements, idKeyCfg, log), nil
|
||||
cfg := config.DefaultForAzureSEVSNP()
|
||||
cfg.Measurements = measurements
|
||||
cfg.FirmwareSignerConfig = idKeyCfg
|
||||
return snp.NewValidator(cfg, log), nil
|
||||
case variant.GCPSEVES{}:
|
||||
return gcp.NewValidator(measurements, log), nil
|
||||
return gcp.NewValidator(config.GCPSEVES{Measurements: measurements}, log), nil
|
||||
case variant.QEMUVTPM{}:
|
||||
return qemu.NewValidator(measurements, log), nil
|
||||
return qemu.NewValidator(config.QEMUVTPM{Measurements: measurements}, log), nil
|
||||
case variant.Dummy{}:
|
||||
return atls.NewFakeValidator(variant.Dummy{}), nil
|
||||
default:
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"encoding/asn1"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -96,7 +96,7 @@ func TestValidator(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
validator, err := Validator(tc.variant, nil, idkeydigest.Config{}, nil)
|
||||
validator, err := Validator(tc.variant, nil, config.SNPFirmwareSignerConfig{}, nil)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -11,8 +11,8 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/gcp",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
"@com_github_google_go_tpm_tools//proto/attest",
|
||||
|
@ -16,8 +16,8 @@ import (
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
"cloud.google.com/go/compute/apiv1/computepb"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
@ -35,12 +35,12 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// NewValidator initializes a new GCP validator with the provided PCR values.
|
||||
func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
|
||||
func NewValidator(cfg config.GCPSEVES, log vtpm.AttestationLogger) *Validator {
|
||||
v := &Validator{
|
||||
restClient: newInstanceClient,
|
||||
}
|
||||
v.Validator = vtpm.NewValidator(
|
||||
pcrs,
|
||||
cfg.Measurements,
|
||||
v.trustedKeyFromGCEAPI,
|
||||
validateCVM,
|
||||
log,
|
||||
|
@ -4,12 +4,11 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "idkeydigest",
|
||||
srcs = [
|
||||
"enforceidkeydigest_string.go",
|
||||
"enforcement_string.go",
|
||||
"idkeydigest.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = ["//internal/cloud/cloudprovider"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
|
@ -1,26 +0,0 @@
|
||||
// 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]]
|
||||
}
|
26
internal/attestation/idkeydigest/enforcement_string.go
Normal file
26
internal/attestation/idkeydigest/enforcement_string.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Code generated by "stringer -type=Enforcement"; 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[Equal-1]
|
||||
_ = x[MAAFallback-2]
|
||||
_ = x[WarnOnly-3]
|
||||
}
|
||||
|
||||
const _Enforcement_name = "UnknownEqualMAAFallbackWarnOnly"
|
||||
|
||||
var _Enforcement_index = [...]uint8{0, 7, 12, 23, 31}
|
||||
|
||||
func (i Enforcement) String() string {
|
||||
if i >= Enforcement(len(_Enforcement_index)-1) {
|
||||
return "Enforcement(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Enforcement_name[_Enforcement_index[i]:_Enforcement_index[i+1]]
|
||||
}
|
@ -4,8 +4,7 @@ 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 provides type definitions for the `idkeydigest` value of SEV-SNP attestation.
|
||||
package idkeydigest
|
||||
|
||||
import (
|
||||
@ -14,59 +13,48 @@ import (
|
||||
"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=Enforcement
|
||||
|
||||
//go:generate stringer -type=EnforceIDKeyDigest
|
||||
// Enforcement defines the behavior of the validator when the ID key digest is not found in the expected list.
|
||||
type Enforcement uint32
|
||||
|
||||
// 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.
|
||||
Unknown Enforcement = iota
|
||||
// Equal will error if the reported signing key digest does not match any of the values in 'acceptedKeyDigests'.
|
||||
Equal
|
||||
// MAAFallback uses 'equal' checking for validation, but fallback to using Microsoft Azure Attestation (MAA)
|
||||
// for validation if the reported digest does not match any of the values in 'acceptedKeyDigests'.
|
||||
MAAFallback
|
||||
// WarnOnly logs a warning if the ID key digest is not found in the expected list.
|
||||
// No error is returned.
|
||||
// WarnOnly is the same as 'equal', but only prints a warning instead of returning an error if no match is found.
|
||||
WarnOnly
|
||||
)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (e *EnforceIDKeyDigest) UnmarshalJSON(b []byte) error {
|
||||
func (e *Enforcement) 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) {
|
||||
func (e Enforcement) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.String())
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (e *EnforceIDKeyDigest) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
func (e *Enforcement) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return e.unmarshal(unmarshal)
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (e EnforceIDKeyDigest) MarshalYAML() (any, error) {
|
||||
func (e Enforcement) MarshalYAML() (any, error) {
|
||||
return e.String(), nil
|
||||
}
|
||||
|
||||
func (e *EnforceIDKeyDigest) unmarshal(unmarshalFunc func(any) error) error {
|
||||
func (e *Enforcement) 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.
|
||||
@ -74,7 +62,7 @@ func (e *EnforceIDKeyDigest) unmarshal(unmarshalFunc func(any) error) error {
|
||||
legacyErr := unmarshalFunc(&legacyEnforce)
|
||||
if legacyErr == nil {
|
||||
if legacyEnforce {
|
||||
*e = StrictChecking
|
||||
*e = Equal
|
||||
} else {
|
||||
*e = WarnOnly
|
||||
}
|
||||
@ -91,18 +79,18 @@ func (e *EnforceIDKeyDigest) unmarshal(unmarshalFunc func(any) error) error {
|
||||
|
||||
*e = EnforcePolicyFromString(enforce)
|
||||
if *e == Unknown {
|
||||
return fmt.Errorf("unknown EnforceIDKeyDigest value: %q", enforce)
|
||||
return fmt.Errorf("unknown Enforcement value: %q", enforce)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnforcePolicyFromString returns EnforceIDKeyDigest from string.
|
||||
func EnforcePolicyFromString(s string) EnforceIDKeyDigest {
|
||||
// EnforcePolicyFromString returns Enforcement from string.
|
||||
func EnforcePolicyFromString(s string) Enforcement {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "strictchecking":
|
||||
return StrictChecking
|
||||
case "equal":
|
||||
return Equal
|
||||
case "maafallback":
|
||||
return MAAFallback
|
||||
case "warnonly":
|
||||
@ -112,37 +100,32 @@ func EnforcePolicyFromString(s string) EnforceIDKeyDigest {
|
||||
}
|
||||
}
|
||||
|
||||
// IDKeyDigests is a list of trusted digest values for the ID key.
|
||||
type IDKeyDigests [][]byte
|
||||
// List is a list of trusted digest values for the ID key.
|
||||
type List [][]byte
|
||||
|
||||
type encodedIDKeyDigests []string
|
||||
type encodedList []string
|
||||
|
||||
// encodedDigestLength is the length of a digest in hex encoding.
|
||||
const encodedDigestLength = 2 * 48
|
||||
|
||||
// NewIDKeyDigests creates a new IDKeyDigests from a list of digests.
|
||||
func NewIDKeyDigests(digests [][]byte) IDKeyDigests {
|
||||
idKeyDigests := make(IDKeyDigests, len(digests))
|
||||
// NewList creates a new IDKeyDigests from a list of digests.
|
||||
func NewList(digests [][]byte) List {
|
||||
idKeyDigests := make(List, len(digests))
|
||||
copy(idKeyDigests, digests)
|
||||
return idKeyDigests
|
||||
}
|
||||
|
||||
// DefaultsFor returns the default IDKeyDigests for the given cloud provider.
|
||||
func DefaultsFor(csp cloudprovider.Provider) IDKeyDigests {
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
return IDKeyDigests{
|
||||
{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96},
|
||||
{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3},
|
||||
{0x93, 0x4f, 0x68, 0xbd, 0x8b, 0xa0, 0x19, 0x38, 0xee, 0xc2, 0x14, 0x75, 0xc8, 0x72, 0xe3, 0xa9, 0x42, 0xb6, 0x0c, 0x59, 0xfa, 0xfc, 0x6d, 0xf9, 0xe9, 0xa7, 0x6e, 0xe6, 0x6b, 0xc4, 0x7f, 0x2d, 0x09, 0xc6, 0x76, 0xf6, 0x1c, 0x03, 0x15, 0xc5, 0x78, 0xda, 0x26, 0x08, 0x5f, 0xb1, 0x3a, 0x71},
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
// DefaultList returns the default list of accepted ID key digests.
|
||||
func DefaultList() List {
|
||||
return List{
|
||||
{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96},
|
||||
{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3},
|
||||
{0x93, 0x4f, 0x68, 0xbd, 0x8b, 0xa0, 0x19, 0x38, 0xee, 0xc2, 0x14, 0x75, 0xc8, 0x72, 0xe3, 0xa9, 0x42, 0xb6, 0x0c, 0x59, 0xfa, 0xfc, 0x6d, 0xf9, 0xe9, 0xa7, 0x6e, 0xe6, 0x6b, 0xc4, 0x7f, 0x2d, 0x09, 0xc6, 0x76, 0xf6, 0x1c, 0x03, 0x15, 0xc5, 0x78, 0xda, 0x26, 0x08, 0x5f, 0xb1, 0x3a, 0x71},
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (d IDKeyDigests) MarshalYAML() (any, error) {
|
||||
func (d List) MarshalYAML() (any, error) {
|
||||
encodedIDKeyDigests := []string{}
|
||||
for _, digest := range d {
|
||||
encodedIDKeyDigests = append(encodedIDKeyDigests, hex.EncodeToString(digest))
|
||||
@ -151,8 +134,8 @@ func (d IDKeyDigests) MarshalYAML() (any, error) {
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (d *IDKeyDigests) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var encodedDigests encodedIDKeyDigests
|
||||
func (d *List) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var encodedDigests encodedList
|
||||
if err := unmarshal(&encodedDigests); err != nil {
|
||||
// Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct.
|
||||
var unmarshalledString string
|
||||
@ -171,7 +154,7 @@ func (d *IDKeyDigests) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (d IDKeyDigests) MarshalJSON() ([]byte, error) {
|
||||
func (d List) MarshalJSON() ([]byte, error) {
|
||||
encodedIDKeyDigests := []string{}
|
||||
for _, digest := range d {
|
||||
encodedIDKeyDigests = append(encodedIDKeyDigests, hex.EncodeToString(digest))
|
||||
@ -180,8 +163,8 @@ func (d IDKeyDigests) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (d *IDKeyDigests) UnmarshalJSON(b []byte) error {
|
||||
var encodedDigests encodedIDKeyDigests
|
||||
func (d *List) UnmarshalJSON(b []byte) error {
|
||||
var encodedDigests encodedList
|
||||
if err := json.Unmarshal(b, &encodedDigests); err != nil {
|
||||
// Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct.
|
||||
var unmarshalledString string
|
||||
@ -200,7 +183,7 @@ func (d *IDKeyDigests) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
// unmarshal is a helper function for unmarshalling encodedIDKeyDigests into IDKeyDigests.
|
||||
func (d *IDKeyDigests) unmarshal(encodedDigests encodedIDKeyDigests) error {
|
||||
func (d *List) unmarshal(encodedDigests encodedList) error {
|
||||
for _, encodedDigest := range encodedDigests {
|
||||
if len(encodedDigest) != encodedDigestLength {
|
||||
return fmt.Errorf("invalid digest length: %d", len(encodedDigest))
|
||||
|
@ -17,12 +17,12 @@ import (
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
dgst IDKeyDigests
|
||||
dgst List
|
||||
wantYAML string
|
||||
wantJSON string
|
||||
}{
|
||||
"digest": {
|
||||
dgst: IDKeyDigests{{0x01, 0x02, 0x03, 0x04}, {0xff, 0xff, 0xff, 0xff}},
|
||||
dgst: List{{0x01, 0x02, 0x03, 0x04}, {0xff, 0xff, 0xff, 0xff}},
|
||||
wantJSON: `["01020304","ffffffff"]`,
|
||||
wantYAML: `
|
||||
- "01020304"
|
||||
@ -58,7 +58,7 @@ func TestUnmarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
yaml string
|
||||
json string
|
||||
wantDgst IDKeyDigests
|
||||
wantDgst List
|
||||
wantErr bool
|
||||
}{
|
||||
"digest struct": {
|
||||
@ -66,20 +66,15 @@ func TestUnmarshal(t *testing.T) {
|
||||
yaml: `
|
||||
- "57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"
|
||||
- "0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3"`,
|
||||
wantDgst: IDKeyDigests{
|
||||
wantDgst: List{
|
||||
{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96},
|
||||
{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3},
|
||||
},
|
||||
},
|
||||
"legacy digest as string": {
|
||||
json: `"57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"`,
|
||||
yaml: `"57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"`,
|
||||
wantDgst: IDKeyDigests{{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96}},
|
||||
},
|
||||
"invalid length": {
|
||||
json: `"010203"`,
|
||||
yaml: `"010203"`,
|
||||
wantDgst: IDKeyDigests{{}},
|
||||
wantDgst: List{{}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@ -91,7 +86,7 @@ func TestUnmarshal(t *testing.T) {
|
||||
|
||||
{
|
||||
// YAML
|
||||
var dgst IDKeyDigests
|
||||
var dgst List
|
||||
err := yaml.Unmarshal([]byte(tc.yaml), &dgst)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
@ -104,7 +99,7 @@ func TestUnmarshal(t *testing.T) {
|
||||
|
||||
{
|
||||
// JSON
|
||||
var dgst IDKeyDigests
|
||||
var dgst List
|
||||
err := json.Unmarshal([]byte(tc.json), &dgst)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
@ -118,16 +113,16 @@ func TestUnmarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnforceIDKeyDigestMarshal(t *testing.T) {
|
||||
func TestEnforcementMarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input EnforceIDKeyDigest
|
||||
input Enforcement
|
||||
wantJSON string
|
||||
wantYAML string
|
||||
}{
|
||||
"strict": {
|
||||
input: StrictChecking,
|
||||
wantJSON: `"StrictChecking"`,
|
||||
wantYAML: "StrictChecking",
|
||||
input: Equal,
|
||||
wantJSON: `"Equal"`,
|
||||
wantYAML: "Equal",
|
||||
},
|
||||
"maaFallback": {
|
||||
input: MAAFallback,
|
||||
@ -163,17 +158,17 @@ func TestEnforceIDKeyDigestMarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnforceIDKeyDigestUnmarshal(t *testing.T) {
|
||||
func TestEnforcementUnmarshal(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
inputJSON string
|
||||
inputYAML string
|
||||
want EnforceIDKeyDigest
|
||||
want Enforcement
|
||||
wantErr bool
|
||||
}{
|
||||
"strict": {
|
||||
inputJSON: `"StrictChecking"`,
|
||||
inputYAML: "StrictChecking",
|
||||
want: StrictChecking,
|
||||
inputJSON: `"Equal"`,
|
||||
inputYAML: "Equal",
|
||||
want: Equal,
|
||||
},
|
||||
"maaFallback": {
|
||||
inputJSON: `"MAAFallback"`,
|
||||
@ -185,16 +180,6 @@ func TestEnforceIDKeyDigestUnmarshal(t *testing.T) {
|
||||
inputYAML: "WarnOnly",
|
||||
want: WarnOnly,
|
||||
},
|
||||
"legacyTrue": {
|
||||
inputJSON: `true`,
|
||||
inputYAML: "true",
|
||||
want: StrictChecking,
|
||||
},
|
||||
"legacyFalse": {
|
||||
inputJSON: `false`,
|
||||
inputYAML: "false",
|
||||
want: WarnOnly,
|
||||
},
|
||||
"invalid": {
|
||||
inputJSON: `"invalid"`,
|
||||
inputYAML: "invalid",
|
||||
@ -214,7 +199,7 @@ func TestEnforceIDKeyDigestUnmarshal(t *testing.T) {
|
||||
|
||||
{
|
||||
// YAML
|
||||
var got EnforceIDKeyDigest
|
||||
var got Enforcement
|
||||
err := yaml.Unmarshal([]byte(tc.inputYAML), &got)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
@ -227,7 +212,7 @@ func TestEnforceIDKeyDigestUnmarshal(t *testing.T) {
|
||||
|
||||
{
|
||||
// JSON
|
||||
var got EnforceIDKeyDigest
|
||||
var got Enforcement
|
||||
err := json.Unmarshal([]byte(tc.inputJSON), &got)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -10,8 +10,8 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/qemu",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
"@com_github_google_go_tpm//tpm2",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
"context"
|
||||
"crypto"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
@ -24,10 +24,10 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// NewValidator initializes a new QEMU validator with the provided PCR values.
|
||||
func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
|
||||
func NewValidator(cfg config.QEMUVTPM, log vtpm.AttestationLogger) *Validator {
|
||||
return &Validator{
|
||||
Validator: vtpm.NewValidator(
|
||||
pcrs,
|
||||
cfg.Measurements,
|
||||
unconditionalTrust,
|
||||
func(vtpm.AttestationDocument, *attest.MachineState) error { return nil },
|
||||
log,
|
||||
|
@ -4,6 +4,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
go_library(
|
||||
name = "config",
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"config.go",
|
||||
"config_doc.go",
|
||||
# keep
|
||||
@ -37,6 +38,7 @@ go_library(
|
||||
go_test(
|
||||
name = "config_test",
|
||||
srcs = [
|
||||
"attestation_test.go",
|
||||
"config_test.go",
|
||||
"validation_test.go",
|
||||
],
|
||||
@ -56,6 +58,7 @@ go_test(
|
||||
"@com_github_spf13_afero//:afero",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
"@org_uber_go_goleak//:goleak",
|
||||
],
|
||||
)
|
||||
|
75
internal/config/attestation.go
Normal file
75
internal/config/attestation.go
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
// AttestationConfig is the common interface for passing attestation configs.
|
||||
type AttestationConfig interface {
|
||||
// GetMeasurements returns the measurements that should be used for attestation.
|
||||
GetMeasurements() measurements.M
|
||||
// GetVariant returns the variant of the attestation config.
|
||||
GetVariant() variant.Variant
|
||||
}
|
||||
|
||||
// Certificate is a wrapper around x509.Certificate allowing custom marshaling.
|
||||
type Certificate x509.Certificate
|
||||
|
||||
// MarshalJSON marshals the certificate to PEM.
|
||||
func (c Certificate) MarshalJSON() ([]byte, error) {
|
||||
pem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
|
||||
return json.Marshal(string(pem))
|
||||
}
|
||||
|
||||
// MarshalYAML marshals the certificate to PEM.
|
||||
func (c Certificate) MarshalYAML() (any, error) {
|
||||
pem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
|
||||
return string(pem), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals the certificate from PEM.
|
||||
func (c *Certificate) UnmarshalJSON(data []byte) error {
|
||||
return c.unmarshal(func(val any) error {
|
||||
return json.Unmarshal(data, val)
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals the certificate from PEM.
|
||||
func (c *Certificate) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return c.unmarshal(unmarshal)
|
||||
}
|
||||
|
||||
func (c *Certificate) unmarshal(unmarshalFunc func(any) error) error {
|
||||
var pemData string
|
||||
if err := unmarshalFunc(&pemData); err != nil {
|
||||
return err
|
||||
}
|
||||
block, _ := pem.Decode([]byte(pemData))
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*c = Certificate(*cert)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustParsePEM(data string) Certificate {
|
||||
jsonData := fmt.Sprintf("\"%s\"", data)
|
||||
var cert Certificate
|
||||
if err := json.Unmarshal([]byte(jsonData), &cert); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cert
|
||||
}
|
46
internal/config/attestation_test.go
Normal file
46
internal/config/attestation_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// testCertPEM is a certificate in PEM format used for unit tests.
|
||||
var testCertPEM = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`
|
||||
|
||||
func TestCertificateMarshalJSON(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
jsonCert := fmt.Sprintf("\"%s\"", testCertPEM)
|
||||
var cert Certificate
|
||||
require.NoError(json.Unmarshal([]byte(jsonCert), &cert))
|
||||
|
||||
out, err := json.Marshal(cert)
|
||||
require.NoError(err)
|
||||
assert.JSONEq(jsonCert, string(out))
|
||||
}
|
||||
|
||||
func TestCertificateMarshalYAML(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
yamlCert := fmt.Sprintf("\"%s\"", testCertPEM)
|
||||
var cert Certificate
|
||||
require.NoError(yaml.Unmarshal([]byte(yamlCert), &cert))
|
||||
|
||||
out, err := yaml.Marshal(cert)
|
||||
require.NoError(err)
|
||||
assert.YAMLEq(yamlCert, string(out))
|
||||
}
|
@ -37,6 +37,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
)
|
||||
|
||||
@ -46,7 +47,7 @@ type Measurements = measurements.M
|
||||
|
||||
// Digests is a required alias since docgen is not able to work with
|
||||
// types in other packages.
|
||||
type Digests = idkeydigest.IDKeyDigests
|
||||
type Digests = idkeydigest.List
|
||||
|
||||
const (
|
||||
// Version2 is the second version number for Constellation config file.
|
||||
@ -175,7 +176,7 @@ type AzureConfig struct {
|
||||
IDKeyDigest Digests `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty"`
|
||||
// description: |
|
||||
// Enforce the specified idKeyDigest value during remote attestation.
|
||||
EnforceIDKeyDigest idkeydigest.EnforceIDKeyDigest `yaml:"enforceIdKeyDigest" validate:"required"`
|
||||
EnforceIDKeyDigest idkeydigest.Enforcement `yaml:"enforceIdKeyDigest" validate:"required"`
|
||||
// description: |
|
||||
// Expected confidential VM measurements.
|
||||
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
|
||||
@ -314,7 +315,7 @@ func Default() *Config {
|
||||
InstanceType: "Standard_DC4as_v5",
|
||||
StateDiskType: "Premium_LRS",
|
||||
DeployCSIDriver: toPtr(true),
|
||||
IDKeyDigest: idkeydigest.DefaultsFor(cloudprovider.Azure),
|
||||
IDKeyDigest: idkeydigest.DefaultList(),
|
||||
EnforceIDKeyDigest: idkeydigest.MAAFallback,
|
||||
SecureBoot: toPtr(false),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
|
||||
@ -504,7 +505,7 @@ func (c *Config) GetMeasurements() measurements.M {
|
||||
}
|
||||
|
||||
// IDKeyDigestPolicy returns the IDKeyDigest checking policy for a cloud provider.
|
||||
func (c *Config) IDKeyDigestPolicy() idkeydigest.EnforceIDKeyDigest {
|
||||
func (c *Config) IDKeyDigestPolicy() idkeydigest.Enforcement {
|
||||
if c.Provider.Azure != nil {
|
||||
return c.Provider.Azure.EnforceIDKeyDigest
|
||||
}
|
||||
@ -512,7 +513,7 @@ func (c *Config) IDKeyDigestPolicy() idkeydigest.EnforceIDKeyDigest {
|
||||
}
|
||||
|
||||
// IDKeyDigests returns the ID Key Digests for the configured cloud provider.
|
||||
func (c *Config) IDKeyDigests() idkeydigest.IDKeyDigests {
|
||||
func (c *Config) IDKeyDigests() idkeydigest.List {
|
||||
if c.Provider.Azure != nil {
|
||||
return c.Provider.Azure.IDKeyDigest
|
||||
}
|
||||
@ -651,6 +652,141 @@ func (c *Config) Validate(force bool) error {
|
||||
return &ValidationError{validationErrMsgs: validationErrMsgs}
|
||||
}
|
||||
|
||||
// AWSNitroTPM is the configuration for AWS Nitro TPM attestation.
|
||||
type AWSNitroTPM struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// GetVariant returns aws-nitro-tpm as the variant.
|
||||
func (AWSNitroTPM) GetVariant() variant.Variant {
|
||||
return variant.AWSNitroTPM{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AWSNitroTPM) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// AzureSEVSNP is the configuration for Azure SEV-SNP attestation.
|
||||
type AzureSEVSNP struct {
|
||||
// description: |
|
||||
// Expected confidential VM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
// description: |
|
||||
// Lowest acceptable bootloader version.
|
||||
BootloaderVersion uint8 `json:"bootloaderVersion" yaml:"bootloaderVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable TEE version.
|
||||
TEEVersion uint8 `json:"teeVersion" yaml:"teeVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable SEV-SNP version.
|
||||
SNPVersion uint8 `json:"snpVersion" yaml:"snpVersion"`
|
||||
// description: |
|
||||
// Lowest acceptable microcode version.
|
||||
MicrocodeVersion uint8 `json:"microcodeVersion" yaml:"microcodeVersion"`
|
||||
// description: |
|
||||
// Configuration for validating the firmware signature.
|
||||
FirmwareSignerConfig SNPFirmwareSignerConfig `json:"firmwareSignerConfig" yaml:"firmwareSignerConfig"`
|
||||
// description: |
|
||||
// AMD Root Key certificate used to verify the SEV-SNP certificate chain.
|
||||
AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"`
|
||||
}
|
||||
|
||||
// DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation.
|
||||
// Version numbers are hard coded and should be updated with each new release.
|
||||
// TODO(AB#3042): replace with dynamic lookup for configurable values.
|
||||
func DefaultForAzureSEVSNP() AzureSEVSNP {
|
||||
return AzureSEVSNP{
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
|
||||
BootloaderVersion: 2,
|
||||
TEEVersion: 0,
|
||||
SNPVersion: 6,
|
||||
MicrocodeVersion: 93,
|
||||
FirmwareSignerConfig: SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.DefaultList(),
|
||||
EnforcementPolicy: idkeydigest.MAAFallback,
|
||||
},
|
||||
// AMD root key. Received from the AMD Key Distribution System API (KDS).
|
||||
AMDRootKey: mustParsePEM(`-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`),
|
||||
}
|
||||
}
|
||||
|
||||
// GetVariant returns azure-sev-snp as the variant.
|
||||
func (AzureSEVSNP) GetVariant() variant.Variant {
|
||||
return variant.AzureSEVSNP{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AzureSEVSNP) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SNPFirmwareSignerConfig is the configuration for validating the firmware signer.
|
||||
type SNPFirmwareSignerConfig struct {
|
||||
// description: |
|
||||
// List of accepted values for the firmware signing key digest.\nValues are enforced according to the 'enforcementPolicy'\n - 'equal' : Error if the reported signing key digest does not match any of the values in 'acceptedKeyDigests'\n - 'maaFallback' : Use 'equal' checking for validation, but fallback to using Microsoft Azure Attestation (MAA) for validation if the reported digest does not match any of the values in 'acceptedKeyDigests'. See the Azure docs for more details: https://learn.microsoft.com/en-us/azure/attestation/overview#amd-sev-snp-attestation\n - 'warnOnly' : Same as 'equal', but only prints a warning instead of returning an error if no match is found
|
||||
AcceptedKeyDigests idkeydigest.List `json:"acceptedKeyDigests" yaml:"acceptedKeyDigests"`
|
||||
// description: |
|
||||
// Key digest enforcement policy. One of {'equal', 'maaFallback', 'warnOnly'}
|
||||
EnforcementPolicy idkeydigest.Enforcement `json:"enforcementPolicy" yaml:"enforcementPolicy" validate:"required"`
|
||||
// description: |
|
||||
// URL of the Microsoft Azure Attestation (MAA) instance to use for fallback validation. Only used if 'enforcementPolicy' is set to 'maaFallback'.
|
||||
MAAURL string `json:"maaURL,omitempty" yaml:"maaURL,omitempty" validate:"len=0"`
|
||||
}
|
||||
|
||||
// AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation.
|
||||
type AzureTrustedLaunch struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// GetVariant returns azure-trusted-launch as the variant.
|
||||
func (AzureTrustedLaunch) GetVariant() variant.Variant {
|
||||
return variant.AzureTrustedLaunch{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AzureTrustedLaunch) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// GCPSEVES is the configuration for GCP SEV-ES attestation.
|
||||
type GCPSEVES struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// GetVariant returns gcp-sev-es as the variant.
|
||||
func (GCPSEVES) GetVariant() variant.Variant {
|
||||
return variant.GCPSEVES{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c GCPSEVES) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// QEMUVTPM is the configuration for QEMU vTPM attestation.
|
||||
type QEMUVTPM struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
}
|
||||
|
||||
// GetVariant returns qemu-vtpm as the variant.
|
||||
func (QEMUVTPM) GetVariant() variant.Variant {
|
||||
return variant.QEMUVTPM{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c QEMUVTPM) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
func toPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
@ -11,13 +11,19 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigDoc encoder.Doc
|
||||
ProviderConfigDoc encoder.Doc
|
||||
AWSConfigDoc encoder.Doc
|
||||
AzureConfigDoc encoder.Doc
|
||||
GCPConfigDoc encoder.Doc
|
||||
OpenStackConfigDoc encoder.Doc
|
||||
QEMUConfigDoc encoder.Doc
|
||||
ConfigDoc encoder.Doc
|
||||
ProviderConfigDoc encoder.Doc
|
||||
AWSConfigDoc encoder.Doc
|
||||
AzureConfigDoc encoder.Doc
|
||||
GCPConfigDoc encoder.Doc
|
||||
OpenStackConfigDoc encoder.Doc
|
||||
QEMUConfigDoc encoder.Doc
|
||||
AWSNitroTPMDoc encoder.Doc
|
||||
AzureSEVSNPDoc encoder.Doc
|
||||
SNPFirmwareSignerConfigDoc encoder.Doc
|
||||
AzureTrustedLaunchDoc encoder.Doc
|
||||
GCPSEVESDoc encoder.Doc
|
||||
QEMUVTPMDoc encoder.Doc
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -229,7 +235,7 @@ func init() {
|
||||
AzureConfigDoc.Fields[12].Description = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
||||
AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
||||
AzureConfigDoc.Fields[13].Name = "enforceIdKeyDigest"
|
||||
AzureConfigDoc.Fields[13].Type = "EnforceIDKeyDigest"
|
||||
AzureConfigDoc.Fields[13].Type = "Enforcement"
|
||||
AzureConfigDoc.Fields[13].Note = ""
|
||||
AzureConfigDoc.Fields[13].Description = "Enforce the specified idKeyDigest value during remote attestation."
|
||||
AzureConfigDoc.Fields[13].Comments[encoder.LineComment] = "Enforce the specified idKeyDigest value during remote attestation."
|
||||
@ -426,6 +432,112 @@ func init() {
|
||||
QEMUConfigDoc.Fields[8].Note = ""
|
||||
QEMUConfigDoc.Fields[8].Description = "Measurement used to enable measured boot."
|
||||
QEMUConfigDoc.Fields[8].Comments[encoder.LineComment] = "Measurement used to enable measured boot."
|
||||
|
||||
AWSNitroTPMDoc.Type = "AWSNitroTPM"
|
||||
AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.Description = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
|
||||
AWSNitroTPMDoc.Fields = make([]encoder.Doc, 1)
|
||||
AWSNitroTPMDoc.Fields[0].Name = "measurements"
|
||||
AWSNitroTPMDoc.Fields[0].Type = "M"
|
||||
AWSNitroTPMDoc.Fields[0].Note = ""
|
||||
AWSNitroTPMDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AWSNitroTPMDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
AzureSEVSNPDoc.Type = "AzureSEVSNP"
|
||||
AzureSEVSNPDoc.Comments[encoder.LineComment] = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
|
||||
AzureSEVSNPDoc.Description = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
|
||||
AzureSEVSNPDoc.Fields = make([]encoder.Doc, 7)
|
||||
AzureSEVSNPDoc.Fields[0].Name = "measurements"
|
||||
AzureSEVSNPDoc.Fields[0].Type = "M"
|
||||
AzureSEVSNPDoc.Fields[0].Note = ""
|
||||
AzureSEVSNPDoc.Fields[0].Description = "Expected confidential VM measurements."
|
||||
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
||||
AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
|
||||
AzureSEVSNPDoc.Fields[1].Type = "uint8"
|
||||
AzureSEVSNPDoc.Fields[1].Note = ""
|
||||
AzureSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
|
||||
AzureSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
|
||||
AzureSEVSNPDoc.Fields[2].Name = "teeVersion"
|
||||
AzureSEVSNPDoc.Fields[2].Type = "uint8"
|
||||
AzureSEVSNPDoc.Fields[2].Note = ""
|
||||
AzureSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
|
||||
AzureSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
|
||||
AzureSEVSNPDoc.Fields[3].Name = "snpVersion"
|
||||
AzureSEVSNPDoc.Fields[3].Type = "uint8"
|
||||
AzureSEVSNPDoc.Fields[3].Note = ""
|
||||
AzureSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
|
||||
AzureSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
|
||||
AzureSEVSNPDoc.Fields[4].Name = "microcodeVersion"
|
||||
AzureSEVSNPDoc.Fields[4].Type = "uint8"
|
||||
AzureSEVSNPDoc.Fields[4].Note = ""
|
||||
AzureSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
|
||||
AzureSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
|
||||
AzureSEVSNPDoc.Fields[5].Name = "firmwareSignerConfig"
|
||||
AzureSEVSNPDoc.Fields[5].Type = "SNPFirmwareSignerConfig"
|
||||
AzureSEVSNPDoc.Fields[5].Note = ""
|
||||
AzureSEVSNPDoc.Fields[5].Description = "Configuration for validating the firmware signature."
|
||||
AzureSEVSNPDoc.Fields[5].Comments[encoder.LineComment] = "Configuration for validating the firmware signature."
|
||||
AzureSEVSNPDoc.Fields[6].Name = "amdRootKey"
|
||||
AzureSEVSNPDoc.Fields[6].Type = "Certificate"
|
||||
AzureSEVSNPDoc.Fields[6].Note = ""
|
||||
AzureSEVSNPDoc.Fields[6].Description = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
|
||||
AzureSEVSNPDoc.Fields[6].Comments[encoder.LineComment] = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
|
||||
|
||||
SNPFirmwareSignerConfigDoc.Type = "SNPFirmwareSignerConfig"
|
||||
SNPFirmwareSignerConfigDoc.Comments[encoder.LineComment] = "SNPFirmwareSignerConfig is the configuration for validating the firmware signer."
|
||||
SNPFirmwareSignerConfigDoc.Description = "SNPFirmwareSignerConfig is the configuration for validating the firmware signer."
|
||||
SNPFirmwareSignerConfigDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AzureSEVSNP",
|
||||
FieldName: "firmwareSignerConfig",
|
||||
},
|
||||
}
|
||||
SNPFirmwareSignerConfigDoc.Fields = make([]encoder.Doc, 3)
|
||||
SNPFirmwareSignerConfigDoc.Fields[0].Name = "acceptedKeyDigests"
|
||||
SNPFirmwareSignerConfigDoc.Fields[0].Type = "List"
|
||||
SNPFirmwareSignerConfigDoc.Fields[0].Note = ""
|
||||
SNPFirmwareSignerConfigDoc.Fields[0].Description = "List of accepted values for the firmware signing key digest.\nValues are enforced according to the 'enforcementPolicy'\n - 'equal' : Error if the reported signing key digest does not match any of the values in 'acceptedKeyDigests'\n - 'maaFallback' : Use 'equal' checking for validation, but fallback to using Microsoft Azure Attestation (MAA) for validation if the reported digest does not match any of the values in 'acceptedKeyDigests'. See the Azure docs for more details: https://learn.microsoft.com/en-us/azure/attestation/overview#amd-sev-snp-attestation\n - 'warnOnly' : Same as 'equal', but only prints a warning instead of returning an error if no match is found"
|
||||
SNPFirmwareSignerConfigDoc.Fields[0].Comments[encoder.LineComment] = "List of accepted values for the firmware signing key digest.\nValues are enforced according to the 'enforcementPolicy'\n - 'equal' : Error if the reported signing key digest does not match any of the values in 'acceptedKeyDigests'\n - 'maaFallback' : Use 'equal' checking for validation, but fallback to using Microsoft Azure Attestation (MAA) for validation if the reported digest does not match any of the values in 'acceptedKeyDigests'. See the Azure docs for more details: https://learn.microsoft.com/en-us/azure/attestation/overview#amd-sev-snp-attestation\n - 'warnOnly' : Same as 'equal', but only prints a warning instead of returning an error if no match is found"
|
||||
SNPFirmwareSignerConfigDoc.Fields[1].Name = "enforcementPolicy"
|
||||
SNPFirmwareSignerConfigDoc.Fields[1].Type = "Enforcement"
|
||||
SNPFirmwareSignerConfigDoc.Fields[1].Note = ""
|
||||
SNPFirmwareSignerConfigDoc.Fields[1].Description = "Key digest enforcement policy. One of {'equal', 'maaFallback', 'warnOnly'}"
|
||||
SNPFirmwareSignerConfigDoc.Fields[1].Comments[encoder.LineComment] = "Key digest enforcement policy. One of {'equal', 'maaFallback', 'warnOnly'}"
|
||||
SNPFirmwareSignerConfigDoc.Fields[2].Name = "maaURL"
|
||||
SNPFirmwareSignerConfigDoc.Fields[2].Type = "string"
|
||||
SNPFirmwareSignerConfigDoc.Fields[2].Note = ""
|
||||
SNPFirmwareSignerConfigDoc.Fields[2].Description = "URL of the Microsoft Azure Attestation (MAA) instance to use for fallback validation. Only used if 'enforcementPolicy' is set to 'maaFallback'."
|
||||
SNPFirmwareSignerConfigDoc.Fields[2].Comments[encoder.LineComment] = "URL of the Microsoft Azure Attestation (MAA) instance to use for fallback validation. Only used if 'enforcementPolicy' is set to 'maaFallback'."
|
||||
|
||||
AzureTrustedLaunchDoc.Type = "AzureTrustedLaunch"
|
||||
AzureTrustedLaunchDoc.Comments[encoder.LineComment] = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation."
|
||||
AzureTrustedLaunchDoc.Description = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation."
|
||||
AzureTrustedLaunchDoc.Fields = make([]encoder.Doc, 1)
|
||||
AzureTrustedLaunchDoc.Fields[0].Name = "measurements"
|
||||
AzureTrustedLaunchDoc.Fields[0].Type = "M"
|
||||
AzureTrustedLaunchDoc.Fields[0].Note = ""
|
||||
AzureTrustedLaunchDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AzureTrustedLaunchDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
GCPSEVESDoc.Type = "GCPSEVES"
|
||||
GCPSEVESDoc.Comments[encoder.LineComment] = "GCPSEVES is the configuration for GCP SEV-ES attestation."
|
||||
GCPSEVESDoc.Description = "GCPSEVES is the configuration for GCP SEV-ES attestation."
|
||||
GCPSEVESDoc.Fields = make([]encoder.Doc, 1)
|
||||
GCPSEVESDoc.Fields[0].Name = "measurements"
|
||||
GCPSEVESDoc.Fields[0].Type = "M"
|
||||
GCPSEVESDoc.Fields[0].Note = ""
|
||||
GCPSEVESDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
GCPSEVESDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
QEMUVTPMDoc.Type = "QEMUVTPM"
|
||||
QEMUVTPMDoc.Comments[encoder.LineComment] = "QEMUVTPM is the configuration for QEMU vTPM attestation."
|
||||
QEMUVTPMDoc.Description = "QEMUVTPM is the configuration for QEMU vTPM attestation."
|
||||
QEMUVTPMDoc.Fields = make([]encoder.Doc, 1)
|
||||
QEMUVTPMDoc.Fields[0].Name = "measurements"
|
||||
QEMUVTPMDoc.Fields[0].Type = "M"
|
||||
QEMUVTPMDoc.Fields[0].Note = ""
|
||||
QEMUVTPMDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
QEMUVTPMDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
}
|
||||
|
||||
func (_ Config) Doc() *encoder.Doc {
|
||||
@ -456,6 +568,30 @@ func (_ QEMUConfig) Doc() *encoder.Doc {
|
||||
return &QEMUConfigDoc
|
||||
}
|
||||
|
||||
func (_ AWSNitroTPM) Doc() *encoder.Doc {
|
||||
return &AWSNitroTPMDoc
|
||||
}
|
||||
|
||||
func (_ AzureSEVSNP) Doc() *encoder.Doc {
|
||||
return &AzureSEVSNPDoc
|
||||
}
|
||||
|
||||
func (_ SNPFirmwareSignerConfig) Doc() *encoder.Doc {
|
||||
return &SNPFirmwareSignerConfigDoc
|
||||
}
|
||||
|
||||
func (_ AzureTrustedLaunch) Doc() *encoder.Doc {
|
||||
return &AzureTrustedLaunchDoc
|
||||
}
|
||||
|
||||
func (_ GCPSEVES) Doc() *encoder.Doc {
|
||||
return &GCPSEVESDoc
|
||||
}
|
||||
|
||||
func (_ QEMUVTPM) Doc() *encoder.Doc {
|
||||
return &QEMUVTPMDoc
|
||||
}
|
||||
|
||||
// GetConfigurationDoc returns documentation for the file ./config_doc.go.
|
||||
func GetConfigurationDoc() *encoder.FileDoc {
|
||||
return &encoder.FileDoc{
|
||||
@ -469,6 +605,12 @@ func GetConfigurationDoc() *encoder.FileDoc {
|
||||
&GCPConfigDoc,
|
||||
&OpenStackConfigDoc,
|
||||
&QEMUConfigDoc,
|
||||
&AWSNitroTPMDoc,
|
||||
&AzureSEVSNPDoc,
|
||||
&SNPFirmwareSignerConfigDoc,
|
||||
&AzureTrustedLaunchDoc,
|
||||
&GCPSEVESDoc,
|
||||
&QEMUVTPMDoc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -739,7 +739,7 @@ func TestConfigVersionCompatibility(t *testing.T) {
|
||||
StateDiskType: "Premium_LRS",
|
||||
ConfidentialVM: toPtr(true),
|
||||
InstanceType: "Standard_DC4as_v5",
|
||||
IDKeyDigest: idkeydigest.IDKeyDigests{{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3}},
|
||||
IDKeyDigest: idkeydigest.List{{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3}},
|
||||
EnforceIDKeyDigest: idkeydigest.WarnOnly,
|
||||
SecureBoot: toPtr(false),
|
||||
DeployCSIDriver: toPtr(true),
|
||||
@ -768,7 +768,7 @@ func TestConfigVersionCompatibility(t *testing.T) {
|
||||
StateDiskType: "Premium_LRS",
|
||||
ConfidentialVM: toPtr(true),
|
||||
InstanceType: "Standard_DC4as_v5",
|
||||
IDKeyDigest: idkeydigest.IDKeyDigests{
|
||||
IDKeyDigest: idkeydigest.List{
|
||||
{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96},
|
||||
{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3},
|
||||
},
|
||||
|
@ -14,6 +14,7 @@ go_library(
|
||||
"//internal/attestation/choose",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/config",
|
||||
"//internal/constants",
|
||||
"//internal/file",
|
||||
"//internal/logger",
|
||||
@ -35,7 +36,7 @@ go_test(
|
||||
"//internal/atls",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/config",
|
||||
"//internal/constants",
|
||||
"//internal/file",
|
||||
"//internal/logger",
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
@ -77,7 +78,7 @@ func (u *Updatable) Update() error {
|
||||
u.log.Debugf("New measurements: %+v", measurements)
|
||||
|
||||
// Read ID Key config
|
||||
var idKeyCfg idkeydigest.Config
|
||||
var idKeyCfg config.SNPFirmwareSignerConfig
|
||||
if u.variant.Equal(variant.AzureSEVSNP{}) {
|
||||
u.log.Infof("Updating SEV-SNP ID Key config")
|
||||
|
||||
@ -91,8 +92,8 @@ func (u *Updatable) Update() error {
|
||||
|
||||
// v2.6 fallback
|
||||
// TODO: Remove after v2.7 release
|
||||
var digest idkeydigest.IDKeyDigests
|
||||
var enforceIDKeyDigest idkeydigest.EnforceIDKeyDigest
|
||||
var digest idkeydigest.List
|
||||
var enforceIDKeyDigest idkeydigest.Enforcement
|
||||
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -110,7 +111,7 @@ func (u *Updatable) Update() error {
|
||||
return fmt.Errorf("unmarshaling content of IDKeyDigestFilename: %s: %w", idkeydigestRaw, err)
|
||||
}
|
||||
|
||||
idKeyCfg.IDKeyDigests = digest
|
||||
idKeyCfg.AcceptedKeyDigests = digest
|
||||
idKeyCfg.EnforcementPolicy = enforceIDKeyDigest
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
@ -82,9 +82,9 @@ func TestNewUpdateableValidator(t *testing.T) {
|
||||
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
|
||||
idkeydigest.Config{
|
||||
IDKeyDigests: idkeydigest.DefaultsFor(cloudprovider.Azure),
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.DefaultList(),
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
))
|
||||
}
|
||||
@ -126,9 +126,9 @@ func TestUpdate(t *testing.T) {
|
||||
))
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
|
||||
idkeydigest.Config{
|
||||
IDKeyDigests: idkeydigest.IDKeyDigests{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
))
|
||||
|
||||
@ -189,9 +189,9 @@ func TestOIDConcurrency(t *testing.T) {
|
||||
))
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
|
||||
idkeydigest.Config{
|
||||
IDKeyDigests: idkeydigest.IDKeyDigests{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
))
|
||||
|
||||
@ -237,9 +237,9 @@ func TestUpdateConcurrency(t *testing.T) {
|
||||
))
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
|
||||
idkeydigest.Config{
|
||||
IDKeyDigests: idkeydigest.IDKeyDigests{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user