config: add attestation variant (#1413)

* Add attestation type to config (optional for now)

* Get attestation variant from config in CLI

* Set attestation variant for Constellation services in helm deployments

* Remove AzureCVM variable from helm deployments

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-03-14 11:46:27 +01:00 committed by GitHub
parent 8679988b6c
commit 6ea5588bdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 379 additions and 383 deletions

View File

@ -21,7 +21,7 @@ type clusterFake struct{}
// InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster. // InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster.
func (c *clusterFake) InitCluster( func (c *clusterFake) InitCluster(
context.Context, string, string, string, []byte, []uint32, bool, bool, context.Context, string, string, string, []byte, []uint32, bool,
[]byte, bool, components.Components, *logger.Logger, []byte, bool, components.Components, *logger.Logger,
) ([]byte, error) { ) ([]byte, error) {
return []byte{}, nil return []byte{}, nil

View File

@ -11,7 +11,6 @@ go_library(
"//bootstrapper/internal/diskencryption", "//bootstrapper/internal/diskencryption",
"//internal/atls", "//internal/atls",
"//internal/attestation", "//internal/attestation",
"//internal/attestation/azure/snp",
"//internal/crypto", "//internal/crypto",
"//internal/file", "//internal/file",
"//internal/grpc/atlscredentials", "//internal/grpc/atlscredentials",

View File

@ -29,7 +29,6 @@ import (
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/diskencryption" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/diskencryption"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation" "github.com/edgelesssys/constellation/v2/internal/attestation"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
"github.com/edgelesssys/constellation/v2/internal/crypto" "github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials" "github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
@ -168,9 +167,6 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
return nil, status.Errorf(codes.Internal, "persisting node state: %s", err) return nil, status.Errorf(codes.Internal, "persisting node state: %s", err)
} }
// Check if we are running on a CVM
_, isCVM := s.issuer.(*snp.Issuer)
clusterName := req.ClusterName clusterName := req.ClusterName
if clusterName == "" { if clusterName == "" {
clusterName = "constellation" clusterName = "constellation"
@ -183,7 +179,6 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
measurementSalt, measurementSalt,
req.EnforcedPcrs, req.EnforcedPcrs,
req.EnforceIdkeydigest, req.EnforceIdkeydigest,
isCVM,
req.HelmDeployments, req.HelmDeployments,
req.ConformanceMode, req.ConformanceMode,
components.NewComponentsFromInitProto(req.KubernetesComponents), components.NewComponentsFromInitProto(req.KubernetesComponents),
@ -260,7 +255,6 @@ type ClusterInitializer interface {
measurementSalt []byte, measurementSalt []byte,
enforcedPcrs []uint32, enforcedPcrs []uint32,
enforceIDKeyDigest bool, enforceIDKeyDigest bool,
azureCVM bool,
helmDeployments []byte, helmDeployments []byte,
conformanceMode bool, conformanceMode bool,
kubernetesComponents components.Components, kubernetesComponents components.Components,

View File

@ -320,7 +320,7 @@ type stubClusterInitializer struct {
} }
func (i *stubClusterInitializer) InitCluster( func (i *stubClusterInitializer) InitCluster(
context.Context, string, string, string, []byte, []uint32, bool, bool, context.Context, string, string, string, []byte, []uint32, bool,
[]byte, bool, components.Components, *logger.Logger, []byte, bool, components.Components, *logger.Logger,
) ([]byte, error) { ) ([]byte, error) {
return i.initClusterKubeconfig, i.initClusterErr return i.initClusterKubeconfig, i.initClusterErr

View File

@ -15,7 +15,6 @@ import (
"fmt" "fmt"
"net" "net"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -82,7 +81,7 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
// InitCluster initializes a new Kubernetes cluster and applies pod network provider. // InitCluster initializes a new Kubernetes cluster and applies pod network provider.
func (k *KubeWrapper) InitCluster( func (k *KubeWrapper) InitCluster(
ctx context.Context, cloudServiceAccountURI, versionString, clusterName string, ctx context.Context, cloudServiceAccountURI, versionString, clusterName string,
measurementSalt []byte, enforcedPCRs []uint32, enforceIDKeyDigest bool, azureCVM bool, measurementSalt []byte, enforcedPCRs []uint32, enforceIDKeyDigest bool,
helmReleasesRaw []byte, conformanceMode bool, kubernetesComponents components.Components, log *logger.Logger, helmReleasesRaw []byte, conformanceMode bool, kubernetesComponents components.Components, log *logger.Logger,
) ([]byte, error) { ) ([]byte, error) {
log.With(zap.String("version", versionString)).Infof("Installing Kubernetes components") log.With(zap.String("version", versionString)).Infof("Installing Kubernetes components")
@ -222,7 +221,7 @@ func (k *KubeWrapper) InitCluster(
return nil, fmt.Errorf("installing constellation-services: %w", err) return nil, fmt.Errorf("installing constellation-services: %w", err)
} }
if err := k.setupInternalConfigMap(ctx, strconv.FormatBool(azureCVM)); err != nil { if err := k.setupInternalConfigMap(ctx); err != nil {
return nil, fmt.Errorf("failed to setup internal ConfigMap: %w", err) return nil, fmt.Errorf("failed to setup internal ConfigMap: %w", err)
} }
@ -319,7 +318,7 @@ func (k *KubeWrapper) setupK8sComponentsConfigMap(ctx context.Context, component
} }
// setupInternalConfigMap applies a ConfigMap (cf. server-side apply) to store information that is not supposed to be user-editable. // setupInternalConfigMap applies a ConfigMap (cf. server-side apply) to store information that is not supposed to be user-editable.
func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context, azureCVM string) error { func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context) error {
config := corev1.ConfigMap{ config := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
APIVersion: "v1", APIVersion: "v1",
@ -329,9 +328,7 @@ func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context, azureCVM strin
Name: constants.InternalConfigMap, Name: constants.InternalConfigMap,
Namespace: "kube-system", Namespace: "kube-system",
}, },
Data: map[string]string{ Data: map[string]string{},
constants.AzureCVM: azureCVM,
},
} }
// We do not use the client's Apply method here since we are handling a kubernetes-native type. // We do not use the client's Apply method here since we are handling a kubernetes-native type.

View File

@ -216,7 +216,7 @@ func TestInitCluster(t *testing.T) {
_, err := kube.InitCluster( _, err := kube.InitCluster(
context.Background(), serviceAccountURI, string(tc.k8sVersion), "kubernetes", context.Background(), serviceAccountURI, string(tc.k8sVersion), "kubernetes",
nil, nil, false, true, []byte("{}"), false, nil, logger.NewTest(t), nil, nil, false, []byte("{}"), false, nil, logger.NewTest(t),
) )
if tc.wantErr { if tc.wantErr {

View File

@ -23,13 +23,9 @@ go_library(
"//cli/internal/libvirt", "//cli/internal/libvirt",
"//cli/internal/terraform", "//cli/internal/terraform",
"//internal/atls", "//internal/atls",
"//internal/attestation/aws", "//internal/attestation/choose",
"//internal/attestation/azure/snp",
"//internal/attestation/azure/trustedlaunch",
"//internal/attestation/gcp",
"//internal/attestation/idkeydigest", "//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared", "//internal/cloud/gcpshared",
"//internal/compatibility", "//internal/compatibility",
@ -37,6 +33,7 @@ go_library(
"//internal/constants", "//internal/constants",
"//internal/kubernetes", "//internal/kubernetes",
"//internal/kubernetes/kubectl", "//internal/kubernetes/kubectl",
"//internal/oid",
"//internal/versions", "//internal/versions",
"//internal/versions/components", "//internal/versions/components",
"//internal/versionsapi", "//internal/versionsapi",
@ -83,6 +80,7 @@ go_test(
"//internal/config", "//internal/config",
"//internal/constants", "//internal/constants",
"//internal/logger", "//internal/logger",
"//internal/oid",
"//internal/versions", "//internal/versions",
"//internal/versions/components", "//internal/versions/components",
"//operators/constellation-node-operator/api/v1alpha1", "//operators/constellation-node-operator/api/v1alpha1",

View File

@ -14,46 +14,40 @@ import (
"fmt" "fmt"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws" "github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"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/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// Validator validates Platform Configuration Registers (PCRs). // Validator validates Platform Configuration Registers (PCRs).
type Validator struct { type Validator struct {
provider cloudprovider.Provider attestationVariant oid.Getter
pcrs measurements.M pcrs measurements.M
idkeydigests idkeydigest.IDKeyDigests idkeydigests idkeydigest.IDKeyDigests
enforceIDKeyDigest bool enforceIDKeyDigest bool
azureCVM bool
validator atls.Validator validator atls.Validator
log debugLog log debugLog
} }
// NewValidator creates a new Validator. // NewValidator creates a new Validator.
func NewValidator(provider cloudprovider.Provider, conf *config.Config, log debugLog) (*Validator, error) { func NewValidator(conf *config.Config, log debugLog) (*Validator, error) {
v := Validator{log: log} v := Validator{log: log}
if provider == cloudprovider.Unknown { variant, err := oid.FromString(conf.AttestationVariant)
return nil, errors.New("unknown cloud provider") if err != nil {
return nil, fmt.Errorf("parsing attestation variant: %w", err)
} }
v.provider = provider v.attestationVariant = variant // valid variant
if err := v.setPCRs(conf); err != nil { if err := v.setPCRs(conf); err != nil {
return nil, err return nil, err
} }
if v.provider == cloudprovider.Azure { if v.attestationVariant.OID().Equal(oid.AzureSEVSNP{}.OID()) {
v.azureCVM = *conf.Provider.Azure.ConfidentialVM v.enforceIDKeyDigest = conf.EnforcesIDKeyDigest()
if v.azureCVM { v.idkeydigests = conf.IDKeyDigests()
v.enforceIDKeyDigest = *conf.Provider.Azure.EnforceIDKeyDigest
v.idkeydigests = conf.Provider.Azure.IDKeyDigest
}
} }
return &v, nil return &v, nil
@ -100,26 +94,26 @@ func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
} }
func (v *Validator) setPCRs(config *config.Config) error { func (v *Validator) setPCRs(config *config.Config) error {
switch v.provider { switch v.attestationVariant {
case cloudprovider.AWS: case oid.AWSNitroTPM{}:
awsPCRs := config.Provider.AWS.Measurements awsPCRs := config.Provider.AWS.Measurements
if len(awsPCRs) == 0 { if len(awsPCRs) == 0 {
return errors.New("no expected measurement provided") return errors.New("no expected measurement provided")
} }
v.pcrs = awsPCRs v.pcrs = awsPCRs
case cloudprovider.Azure: case oid.AzureSEVSNP{}, oid.AzureTrustedLaunch{}:
azurePCRs := config.Provider.Azure.Measurements azurePCRs := config.Provider.Azure.Measurements
if len(azurePCRs) == 0 { if len(azurePCRs) == 0 {
return errors.New("no expected measurement provided") return errors.New("no expected measurement provided")
} }
v.pcrs = azurePCRs v.pcrs = azurePCRs
case cloudprovider.GCP: case oid.GCPSEVES{}:
gcpPCRs := config.Provider.GCP.Measurements gcpPCRs := config.Provider.GCP.Measurements
if len(gcpPCRs) == 0 { if len(gcpPCRs) == 0 {
return errors.New("no expected measurement provided") return errors.New("no expected measurement provided")
} }
v.pcrs = gcpPCRs v.pcrs = gcpPCRs
case cloudprovider.QEMU: case oid.QEMUVTPM{}:
qemuPCRs := config.Provider.QEMU.Measurements qemuPCRs := config.Provider.QEMU.Measurements
if len(qemuPCRs) == 0 { if len(qemuPCRs) == 0 {
return errors.New("no expected measurement provided") return errors.New("no expected measurement provided")
@ -142,20 +136,9 @@ func (v *Validator) PCRS() measurements.M {
func (v *Validator) updateValidator(cmd *cobra.Command) { func (v *Validator) updateValidator(cmd *cobra.Command) {
log := warnLogger{cmd: cmd, log: v.log} log := warnLogger{cmd: cmd, log: v.log}
switch v.provider {
case cloudprovider.GCP: // Use of a valid variant has been check in NewValidator so we may drop the error
v.validator = gcp.NewValidator(v.pcrs, log) v.validator, _ = choose.Validator(v.attestationVariant, v.pcrs, v.idkeydigests, v.enforceIDKeyDigest, log)
case cloudprovider.Azure:
if v.azureCVM {
v.validator = snp.NewValidator(v.pcrs, v.idkeydigests, v.enforceIDKeyDigest, log)
} else {
v.validator = trustedlaunch.NewValidator(v.pcrs, log)
}
case cloudprovider.AWS:
v.validator = aws.NewValidator(v.pcrs, log)
case cloudprovider.QEMU:
v.validator = qemu.NewValidator(v.pcrs, log)
}
} }
// warnLogger implements logging of warnings for validators. // warnLogger implements logging of warnings for validators.

View File

@ -19,11 +19,12 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu" "github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestNewValidator(t *testing.T) { func TestNewValidator(t *testing.T) {
@ -37,47 +38,82 @@ func TestNewValidator(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
provider cloudprovider.Provider config *config.Config
config *config.Config wantErr bool
pcrs measurements.M
enforceIDKeyDigest bool
digest idkeydigest.IDKeyDigests
azureCVM bool
wantErr bool
}{ }{
"gcp": { "gcp": {
provider: cloudprovider.GCP, config: &config.Config{
pcrs: testPCRs, AttestationVariant: oid.GCPSEVES{}.String(),
Provider: config.ProviderConfig{
GCP: &config.GCPConfig{
Measurements: testPCRs,
},
},
},
}, },
"azure cvm": { "azure cvm": {
provider: cloudprovider.Azure, config: &config.Config{
pcrs: testPCRs, AttestationVariant: oid.AzureSEVSNP{}.String(),
azureCVM: true, Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
}, },
"azure trusted launch": { "azure trusted launch": {
provider: cloudprovider.Azure, config: &config.Config{
pcrs: testPCRs, AttestationVariant: oid.AzureTrustedLaunch{}.String(),
azureCVM: false, Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
}, },
"qemu": { "qemu": {
provider: cloudprovider.QEMU, config: &config.Config{
pcrs: testPCRs, AttestationVariant: oid.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
}, },
"no pcrs provided": { "no pcrs provided": {
provider: cloudprovider.Azure, config: &config.Config{
pcrs: measurements.M{}, AttestationVariant: oid.AzureSEVSNP{}.String(),
wantErr: true, Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: measurements.M{},
},
},
},
wantErr: true,
}, },
"unknown provider": { "unknown variant": {
provider: cloudprovider.Unknown, config: &config.Config{
pcrs: testPCRs, AttestationVariant: "unknown",
wantErr: true, Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
wantErr: true,
}, },
"set idkeydigest": { "set idkeydigest": {
provider: cloudprovider.Azure, config: &config.Config{
pcrs: testPCRs, AttestationVariant: oid.AzureSEVSNP{}.String(),
digest: idkeydigest.IDKeyDigests{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")}, Provider: config.ProviderConfig{
enforceIDKeyDigest: true, Azure: &config.AzureConfig{
Measurements: testPCRs,
IDKeyDigest: idkeydigest.IDKeyDigests{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
EnforceIDKeyDigest: &[]bool{true}[0],
},
},
},
}, },
} }
@ -85,25 +121,16 @@ func TestNewValidator(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
conf := &config.Config{Provider: config.ProviderConfig{}} validators, err := NewValidator(tc.config, logger.NewTest(t))
if tc.provider == cloudprovider.GCP {
conf.Provider.GCP = &config.GCPConfig{Measurements: tc.pcrs}
}
if tc.provider == cloudprovider.Azure {
conf.Provider.Azure = &config.AzureConfig{Measurements: tc.pcrs, EnforceIDKeyDigest: &tc.enforceIDKeyDigest, IDKeyDigest: tc.digest, ConfidentialVM: &tc.azureCVM}
}
if tc.provider == cloudprovider.QEMU {
conf.Provider.QEMU = &config.QEMUConfig{Measurements: tc.pcrs}
}
validators, err := NewValidator(tc.provider, conf, logger.NewTest(t))
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
} else { } else {
assert.NoError(err) assert.NoError(err)
assert.Equal(tc.pcrs, validators.pcrs) assert.Equal(tc.config.GetMeasurements(), validators.pcrs)
assert.Equal(tc.provider, validators.provider) variant, err := oid.FromString(tc.config.AttestationVariant)
require.NoError(t, err)
assert.Equal(variant, validators.attestationVariant)
} }
}) })
} }
@ -129,31 +156,29 @@ func TestValidatorV(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
provider cloudprovider.Provider variant oid.Getter
pcrs measurements.M pcrs measurements.M
wantVs atls.Validator wantVs atls.Validator
azureCVM bool
}{ }{
"gcp": { "gcp": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
wantVs: gcp.NewValidator(newTestPCRs(), nil), wantVs: gcp.NewValidator(newTestPCRs(), nil),
}, },
"azure cvm": { "azure cvm": {
provider: cloudprovider.Azure, variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
wantVs: snp.NewValidator(newTestPCRs(), idkeydigest.IDKeyDigests{}, false, nil), wantVs: snp.NewValidator(newTestPCRs(), idkeydigest.IDKeyDigests{}, false, nil),
azureCVM: true,
}, },
"azure trusted launch": { "azure trusted launch": {
provider: cloudprovider.Azure, variant: oid.AzureTrustedLaunch{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
wantVs: trustedlaunch.NewValidator(newTestPCRs(), nil), wantVs: trustedlaunch.NewValidator(newTestPCRs(), nil),
}, },
"qemu": { "qemu": {
provider: cloudprovider.QEMU, variant: oid.QEMUVTPM{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
wantVs: qemu.NewValidator(newTestPCRs(), nil), wantVs: qemu.NewValidator(newTestPCRs(), nil),
}, },
} }
@ -161,7 +186,7 @@ func TestValidatorV(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
validators := &Validator{provider: tc.provider, pcrs: tc.pcrs, azureCVM: tc.azureCVM} validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
resultValidator := validators.V(&cobra.Command{}) resultValidator := validators.V(&cobra.Command{})
@ -206,53 +231,53 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
provider cloudprovider.Provider variant oid.Getter
pcrs measurements.M pcrs measurements.M
ownerID string ownerID string
clusterID string clusterID string
wantErr bool wantErr bool
}{ }{
"gcp update owner ID": { "gcp update owner ID": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
ownerID: one64, ownerID: one64,
}, },
"gcp update cluster ID": { "gcp update cluster ID": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
clusterID: one64, clusterID: one64,
}, },
"gcp update both": { "gcp update both": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
ownerID: one64, ownerID: one64,
clusterID: one64, clusterID: one64,
}, },
"azure update owner ID": { "azure update owner ID": {
provider: cloudprovider.Azure, variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
ownerID: one64, ownerID: one64,
}, },
"azure update cluster ID": { "azure update cluster ID": {
provider: cloudprovider.Azure, variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
clusterID: one64, clusterID: one64,
}, },
"azure update both": { "azure update both": {
provider: cloudprovider.Azure, variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
ownerID: one64, ownerID: one64,
clusterID: one64, clusterID: one64,
}, },
"owner ID and cluster ID empty": { "owner ID and cluster ID empty": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
}, },
"invalid encoding": { "invalid encoding": {
provider: cloudprovider.GCP, variant: oid.GCPSEVES{},
pcrs: newTestPCRs(), pcrs: newTestPCRs(),
ownerID: "invalid", ownerID: "invalid",
wantErr: true, wantErr: true,
}, },
} }
@ -260,7 +285,7 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
validators := &Validator{provider: tc.provider, pcrs: tc.pcrs} validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
err := validators.UpdateInitPCRs(tc.ownerID, tc.clusterID) err := validators.UpdateInitPCRs(tc.ownerID, tc.clusterID)
@ -392,8 +417,8 @@ func TestUpdatePCR(t *testing.T) {
} }
validators := &Validator{ validators := &Validator{
provider: cloudprovider.GCP, attestationVariant: oid.GCPSEVES{},
pcrs: pcrs, pcrs: pcrs,
} }
err := validators.updatePCR(tc.pcrIndex, tc.encoded) err := validators.updatePCR(tc.pcrIndex, tc.encoded)

View File

@ -59,6 +59,7 @@ go_library(
"//internal/kubernetes/kubectl", "//internal/kubernetes/kubectl",
"//internal/license", "//internal/license",
"//internal/logger", "//internal/logger",
"//internal/oid",
"//internal/retry", "//internal/retry",
"//internal/sigstore", "//internal/sigstore",
"//internal/versions", "//internal/versions",

View File

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -105,6 +106,18 @@ func createConfig(provider cloudprovider.Provider) *config.Config {
conf.StateDiskSizeGB = 10 conf.StateDiskSizeGB = 10
} }
// TODO(AB#2976): Replace hardcoded values with user input
switch provider {
case cloudprovider.AWS:
conf.AttestationVariant = oid.AWSNitroTPM{}.String()
case cloudprovider.Azure:
conf.AttestationVariant = oid.AzureSEVSNP{}.String()
case cloudprovider.GCP:
conf.AttestationVariant = oid.GCPSEVES{}.String()
case cloudprovider.QEMU:
conf.AttestationVariant = oid.QEMUVTPM{}.String()
}
return conf return conf
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -92,6 +93,9 @@ func TestConfigGenerateDefaultGCPSpecific(t *testing.T) {
cg := &configGenerateCmd{log: logger.NewTest(t)} cg := &configGenerateCmd{log: logger.NewTest(t)}
require.NoError(cg.configGenerate(cmd, fileHandler, cloudprovider.GCP)) require.NoError(cg.configGenerate(cmd, fileHandler, cloudprovider.GCP))
// TODO(AB#2976): Remove this once attestation variants are dynamically created
wantConf.AttestationVariant = oid.GCPSEVES{}.String()
var readConfig config.Config var readConfig config.Config
err := fileHandler.ReadYAML(constants.ConfigFilename, &readConfig) err := fileHandler.ReadYAML(constants.ConfigFilename, &readConfig)
assert.NoError(err) assert.NoError(err)

View File

@ -135,7 +135,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
cmd.PrintErrf("License check failed: %v", err) cmd.PrintErrf("License check failed: %v", err)
} }
i.log.Debugf("Checked license") i.log.Debugf("Checked license")
validator, err := cloudcmd.NewValidator(provider, conf, i.log) validator, err := cloudcmd.NewValidator(conf, i.log)
if err != nil { if err != nil {
return err return err
} }

View File

@ -436,6 +436,7 @@ func TestAttestation(t *testing.T) {
cfg := config.Default() cfg := config.Default()
cfg.Image = "image" cfg.Image = "image"
cfg.AttestationVariant = oid.QEMUVTPM{}.String()
cfg.RemoveProviderExcept(cloudprovider.QEMU) cfg.RemoveProviderExcept(cloudprovider.QEMU)
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, false) cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, false)
cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, false) cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, false)
@ -529,6 +530,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
switch csp { switch csp {
case cloudprovider.Azure: case cloudprovider.Azure:
conf.AttestationVariant = oid.AzureSEVSNP{}.String()
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab" conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.Location = "test-location" conf.Provider.Azure.Location = "test-location"
@ -540,6 +542,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, false) conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, false)
conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, false) conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, false)
case cloudprovider.GCP: case cloudprovider.GCP:
conf.AttestationVariant = oid.GCPSEVES{}.String()
conf.Provider.GCP.Region = "test-region" conf.Provider.GCP.Region = "test-region"
conf.Provider.GCP.Project = "test-project" conf.Provider.GCP.Project = "test-project"
conf.Provider.GCP.Zone = "test-zone" conf.Provider.GCP.Zone = "test-zone"
@ -548,6 +551,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, false) conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, false)
conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, false) conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, false)
case cloudprovider.QEMU: case cloudprovider.QEMU:
conf.AttestationVariant = oid.QEMUVTPM{}.String()
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, false) conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, false)
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, false) conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, false)
conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, false) conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, false)

View File

@ -95,7 +95,7 @@ func (r *recoverCmd) recover(
interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances
} }
validator, err := cloudcmd.NewValidator(provider, conf, r.log) validator, err := cloudcmd.NewValidator(conf, r.log)
if err != nil { if err != nil {
return err return err
} }

View File

@ -86,7 +86,7 @@ func (v *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
provider := conf.GetProvider() provider := conf.GetProvider()
v.log.Debugf("Creating aTLS Validator for %s", provider) v.log.Debugf("Creating aTLS Validator for %s", provider)
validators, err := cloudcmd.NewValidator(provider, conf, v.log) validators, err := cloudcmd.NewValidator(conf, v.log)
if err != nil { if err != nil {
return err return err
} }

View File

@ -357,6 +357,7 @@ go_test(
"//internal/deploy/helm", "//internal/deploy/helm",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/oid",
"@com_github_pkg_errors//:errors", "@com_github_pkg_errors//:errors",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View File

@ -39,6 +39,7 @@ spec:
args: args:
- --cloud-provider={{ .Values.csp }} - --cloud-provider={{ .Values.csp }}
- --key-service-endpoint=key-service.{{ .Release.Namespace }}:{{ .Values.global.keyServicePort }} - --key-service-endpoint=key-service.{{ .Release.Namespace }}:{{ .Values.global.keyServicePort }}
- --attestation-variant={{ .Values.attestationVariant }}
volumeMounts: volumeMounts:
- mountPath: {{ .Values.global.serviceBasePath | quote }} - mountPath: {{ .Values.global.serviceBasePath | quote }}
name: config name: config

View File

@ -28,13 +28,19 @@
"description": "Salt used to generate node measurements", "description": "Salt used to generate node measurements",
"type": "string", "type": "string",
"examples": ["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"] "examples": ["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]
},
"attestationVariant": {
"description": "Attestation variant to use for aTLS connections.",
"type": "string",
"examples": ["azure-sev-snp", "azure-trusted-launch", "gcp-sev-es"]
} }
}, },
"required": [ "required": [
"csp", "csp",
"measurements", "measurements",
"measurementSalt", "measurementSalt",
"image" "image",
"attestationVariant"
], ],
"if": { "if": {
"properties": { "csp": { "const": "azure" } }, "properties": { "csp": { "const": "azure" } },

View File

@ -1,3 +1,4 @@
csp: "gcp" csp: "gcp"
attestationVariant: ""
joinServicePort: 9090 joinServicePort: 9090
joinServiceNodePort: 30090 joinServiceNodePort: 30090

View File

@ -17,7 +17,7 @@ spec:
spec: spec:
containers: containers:
- args: - args:
- --cloud-provider={{ .Values.csp }} - --attestation-variant={{ .Values.attestationVariant }}
image: {{ .Values.image | quote }} image: {{ .Values.image | quote }}
name: verification-service name: verification-service
ports: ports:

View File

@ -1,10 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft-07/schema#", "$schema": "https://json-schema.org/draft-07/schema#",
"properties": { "properties": {
"csp": {
"description": "CSP to which the chart is deployed.",
"enum": ["Azure", "GCP", "AWS", "QEMU"]
},
"image": { "image": {
"description": "Container image to use for the spawned pods.", "description": "Container image to use for the spawned pods.",
"type": "string", "type": "string",
@ -13,12 +9,17 @@
"loadBalancerIP": { "loadBalancerIP": {
"description": "IP of the k8s LB service", "description": "IP of the k8s LB service",
"type": "string" "type": "string"
},
"attestationVariant": {
"description": "Attestation variant to use for aTLS connections.",
"type": "string",
"examples": ["azure-sev-snp", "azure-trusted-launch", "gcp-sev-es"]
} }
}, },
"required": [ "required": [
"csp",
"image", "image",
"loadBalancerIP" "loadBalancerIP",
"attestationVariant"
], ],
"title": "Values", "title": "Values",
"type": "object" "type": "object"

View File

@ -1,3 +1,5 @@
image: ""
attestationVariant: ""
httpContainerPort: 8080 httpContainerPort: 8080
grpcContainerPort: 9090 grpcContainerPort: 9090
httpNodePort: 30080 httpNodePort: 30080

View File

@ -415,7 +415,6 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
"image": i.autoscalerImage, "image": i.autoscalerImage,
}, },
"verification-service": map[string]any{ "verification-service": map[string]any{
"csp": i.csp.String(),
"image": i.verificationServiceImage, "image": i.verificationServiceImage,
}, },
"gcp-guest-agent": map[string]any{ "gcp-guest-agent": map[string]any{
@ -491,6 +490,18 @@ func extendConstellationServicesValues(in map[string]any, config *config.Config,
keyServiceValues["masterSecret"] = base64.StdEncoding.EncodeToString(masterSecret) keyServiceValues["masterSecret"] = base64.StdEncoding.EncodeToString(masterSecret)
keyServiceValues["salt"] = base64.StdEncoding.EncodeToString(salt) keyServiceValues["salt"] = base64.StdEncoding.EncodeToString(salt)
joinServiceVals, ok := in["join-service"].(map[string]any)
if !ok {
return errors.New("invalid join-service values")
}
joinServiceVals["attestationVariant"] = config.AttestationVariant
verifyServiceVals, ok := in["verification-service"].(map[string]any)
if !ok {
return errors.New("invalid verification-service values")
}
verifyServiceVals["attestationVariant"] = config.AttestationVariant
csp := config.GetProvider() csp := config.GetProvider()
switch csp { switch csp {
case cloudprovider.Azure: case cloudprovider.Azure:

View File

@ -22,6 +22,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm" "github.com/edgelesssys/constellation/v2/internal/deploy/helm"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -59,25 +60,34 @@ func TestConstellationServices(t *testing.T) {
cnmImage string cnmImage string
}{ }{
"GCP": { "GCP": {
config: &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{ config: &config.Config{
DeployCSIDriver: func() *bool { b := true; return &b }(), AttestationVariant: oid.GCPSEVES{}.String(),
}}}, Provider: config.ProviderConfig{GCP: &config.GCPConfig{
DeployCSIDriver: toPtr(true),
}},
},
enforceIDKeyDigest: false, enforceIDKeyDigest: false,
valuesModifier: prepareGCPValues, valuesModifier: prepareGCPValues,
ccmImage: "ccmImageForGCP", ccmImage: "ccmImageForGCP",
}, },
"Azure": { "Azure": {
config: &config.Config{Provider: config.ProviderConfig{Azure: &config.AzureConfig{ config: &config.Config{
DeployCSIDriver: func() *bool { b := true; return &b }(), AttestationVariant: oid.AzureSEVSNP{}.String(),
EnforceIDKeyDigest: func() *bool { b := true; return &b }(), Provider: config.ProviderConfig{Azure: &config.AzureConfig{
}}}, DeployCSIDriver: toPtr(true),
EnforceIDKeyDigest: toPtr(true),
}},
},
enforceIDKeyDigest: true, enforceIDKeyDigest: true,
valuesModifier: prepareAzureValues, valuesModifier: prepareAzureValues,
ccmImage: "ccmImageForAzure", ccmImage: "ccmImageForAzure",
cnmImage: "cnmImageForAzure", cnmImage: "cnmImageForAzure",
}, },
"QEMU": { "QEMU": {
config: &config.Config{Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}}, config: &config.Config{
AttestationVariant: oid.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
enforceIDKeyDigest: false, enforceIDKeyDigest: false,
valuesModifier: prepareQEMUValues, valuesModifier: prepareQEMUValues,
}, },
@ -430,3 +440,7 @@ func prepareQEMUValues(values map[string]any) error {
return nil return nil
} }
func toPtr[T any](v T) *T {
return &v
}

View File

@ -39,6 +39,7 @@ spec:
args: args:
- --cloud-provider=Azure - --cloud-provider=Azure
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=azure-sev-snp
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config

View File

@ -17,7 +17,7 @@ spec:
spec: spec:
containers: containers:
- args: - args:
- --cloud-provider=Azure - --attestation-variant=azure-sev-snp
image: verificationImage image: verificationImage
name: verification-service name: verification-service
ports: ports:

View File

@ -39,6 +39,7 @@ spec:
args: args:
- --cloud-provider=GCP - --cloud-provider=GCP
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=gcp-sev-es
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config

View File

@ -17,7 +17,7 @@ spec:
spec: spec:
containers: containers:
- args: - args:
- --cloud-provider=GCP - --attestation-variant=gcp-sev-es
image: verificationImage image: verificationImage
name: verification-service name: verification-service
ports: ports:

View File

@ -39,6 +39,7 @@ spec:
args: args:
- --cloud-provider=QEMU - --cloud-provider=QEMU
- --key-service-endpoint=key-service.testNamespace:9000 - --key-service-endpoint=key-service.testNamespace:9000
- --attestation-variant=qemu-vtpm
volumeMounts: volumeMounts:
- mountPath: /var/config - mountPath: /var/config
name: config name: config

View File

@ -17,7 +17,7 @@ spec:
spec: spec:
containers: containers:
- args: - args:
- --cloud-provider=QEMU - --attestation-variant=qemu-vtpm
image: verificationImage image: verificationImage
name: verification-service name: verification-service
ports: ports:

View File

@ -22,6 +22,7 @@ go_library(
"//internal/config/instancetypes", "//internal/config/instancetypes",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/oid",
"//internal/versions", "//internal/versions",
"//internal/versionsapi", "//internal/versionsapi",
"@com_github_go_playground_locales//en", "@com_github_go_playground_locales//en",
@ -48,6 +49,7 @@ go_test(
"//internal/config/instancetypes", "//internal/config/instancetypes",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/oid",
"@com_github_go_playground_locales//en", "@com_github_go_playground_locales//en",
"@com_github_go_playground_universal_translator//:universal-translator", "@com_github_go_playground_universal_translator//:universal-translator",
"@com_github_go_playground_validator_v10//:validator", "@com_github_go_playground_validator_v10//:validator",

View File

@ -77,6 +77,9 @@ type Config struct {
// DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md // DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md
DebugCluster *bool `yaml:"debugCluster" validate:"required"` DebugCluster *bool `yaml:"debugCluster" validate:"required"`
// description: | // description: |
// Attestation variant used to verify the integrity of a node.
AttestationVariant string `yaml:"attestationVariant" validate:"valid_attestation_variant"` // TODO: v2.8: Mark required
// description: |
// Supported cloud providers and their specific configurations. // Supported cloud providers and their specific configurations.
Provider ProviderConfig `yaml:"provider" validate:"dive"` Provider ProviderConfig `yaml:"provider" validate:"dive"`
// description: | // description: |
@ -573,10 +576,15 @@ func (c *Config) Validate(force bool) error {
if err := validate.RegisterTranslation("version_compatibility", trans, registerVersionCompatibilityError, translateVersionCompatibilityError); err != nil { if err := validate.RegisterTranslation("version_compatibility", trans, registerVersionCompatibilityError, translateVersionCompatibilityError); err != nil {
return err return err
} }
if err := validate.RegisterTranslation("valid_name", trans, registerValidateNameError, c.translateValidateNameError); err != nil { if err := validate.RegisterTranslation("valid_name", trans, registerValidateNameError, c.translateValidateNameError); err != nil {
return err return err
} }
if err := validate.RegisterTranslation("valid_attestation_variant", trans, registerValidAttestVariantError, c.translateValidAttestVariantError); err != nil {
return err
}
if err := validate.RegisterValidation("valid_name", c.validateName); err != nil { if err := validate.RegisterValidation("valid_name", c.validateName); err != nil {
return err return err
} }
@ -613,6 +621,10 @@ func (c *Config) Validate(force bool) error {
return err return err
} }
if err := validate.RegisterValidation("valid_attestation_variant", c.validAttestVariant); err != nil {
return err
}
// Register provider validation // Register provider validation
validate.RegisterStructValidation(validateProvider, ProviderConfig{}) validate.RegisterStructValidation(validateProvider, ProviderConfig{})

View File

@ -25,7 +25,7 @@ func init() {
ConfigDoc.Type = "Config" ConfigDoc.Type = "Config"
ConfigDoc.Comments[encoder.LineComment] = "Config defines configuration used by CLI." ConfigDoc.Comments[encoder.LineComment] = "Config defines configuration used by CLI."
ConfigDoc.Description = "Config defines configuration used by CLI." ConfigDoc.Description = "Config defines configuration used by CLI."
ConfigDoc.Fields = make([]encoder.Doc, 9) ConfigDoc.Fields = make([]encoder.Doc, 10)
ConfigDoc.Fields[0].Name = "version" ConfigDoc.Fields[0].Name = "version"
ConfigDoc.Fields[0].Type = "string" ConfigDoc.Fields[0].Type = "string"
ConfigDoc.Fields[0].Note = "" ConfigDoc.Fields[0].Note = ""
@ -61,18 +61,23 @@ func init() {
ConfigDoc.Fields[6].Note = "" ConfigDoc.Fields[6].Note = ""
ConfigDoc.Fields[6].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md" ConfigDoc.Fields[6].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
ConfigDoc.Fields[6].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md" ConfigDoc.Fields[6].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
ConfigDoc.Fields[7].Name = "provider" ConfigDoc.Fields[7].Name = "attestationVariant"
ConfigDoc.Fields[7].Type = "ProviderConfig" ConfigDoc.Fields[7].Type = "string"
ConfigDoc.Fields[7].Note = "" ConfigDoc.Fields[7].Note = "TODO: v2.8: Mark required\n"
ConfigDoc.Fields[7].Description = "Supported cloud providers and their specific configurations." ConfigDoc.Fields[7].Description = "Attestation variant used to verify the integrity of a node."
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations." ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Attestation variant used to verify the integrity of a node."
ConfigDoc.Fields[8].Name = "upgrade" ConfigDoc.Fields[8].Name = "provider"
ConfigDoc.Fields[8].Type = "UpgradeConfig" ConfigDoc.Fields[8].Type = "ProviderConfig"
ConfigDoc.Fields[8].Note = "" ConfigDoc.Fields[8].Note = ""
ConfigDoc.Fields[8].Description = "Configuration to apply during constellation upgrade." ConfigDoc.Fields[8].Description = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[8].Comments[encoder.LineComment] = "Configuration to apply during constellation upgrade." ConfigDoc.Fields[8].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[9].Name = "upgrade"
ConfigDoc.Fields[9].Type = "UpgradeConfig"
ConfigDoc.Fields[9].Note = ""
ConfigDoc.Fields[9].Description = "Configuration to apply during constellation upgrade."
ConfigDoc.Fields[9].Comments[encoder.LineComment] = "Configuration to apply during constellation upgrade."
ConfigDoc.Fields[8].AddExample("", UpgradeConfig{Image: "", Measurements: Measurements{}}) ConfigDoc.Fields[9].AddExample("", UpgradeConfig{Image: "", Measurements: Measurements{}})
UpgradeConfigDoc.Type = "UpgradeConfig" UpgradeConfigDoc.Type = "UpgradeConfig"
UpgradeConfigDoc.Comments[encoder.LineComment] = "UpgradeConfig defines configuration used during constellation upgrade." UpgradeConfigDoc.Comments[encoder.LineComment] = "UpgradeConfig defines configuration used during constellation upgrade."

View File

@ -16,6 +16,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes" "github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/go-playground/locales/en" "github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@ -122,6 +123,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default() c := Default()
c.RemoveProviderExcept(cloudprovider.Azure) c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo() c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = oid.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5" c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa" c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus" c.Provider.Azure.Location = "westus"
@ -141,6 +143,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default() c := Default()
c.RemoveProviderExcept(cloudprovider.Azure) c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo() c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = oid.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5" c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa" c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus" c.Provider.Azure.Location = "westus"
@ -232,6 +235,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.Image = "v" + constants.VersionInfo() cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = oid.AzureSEVSNP{}.String()
az := cnf.Provider.Azure az := cnf.Provider.Azure
az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
az.TenantID = "01234567-0123-0123-0123-0123456789ab" az.TenantID = "01234567-0123-0123-0123-0123456789ab"
@ -261,6 +265,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.Image = "v" + constants.VersionInfo() cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = oid.GCPSEVES{}.String()
gcp := cnf.Provider.GCP gcp := cnf.Provider.GCP
gcp.Region = "test-region" gcp.Region = "test-region"
gcp.Project = "test-project" gcp.Project = "test-project"

View File

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes" "github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/edgelesssys/constellation/v2/internal/versionsapi"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
@ -466,3 +467,55 @@ func (c *Config) validateName(fl validator.FieldLevel) bool {
} }
return len(fl.Field().String()) <= constants.ConstellationNameLength return len(fl.Field().String()) <= constants.ConstellationNameLength
} }
func registerValidAttestVariantError(ut ut.Translator) error {
return ut.Add("valid_attestation_variant", `"{0}" is not a valid attestation variant for CSP {1}`, true)
}
func (c *Config) translateValidAttestVariantError(ut ut.Translator, fe validator.FieldError) string {
csp := c.GetProvider()
t, _ := ut.T("valid_attestation_variant", c.AttestationVariant, csp.String())
return t
}
func (c *Config) validAttestVariant(fl validator.FieldLevel) bool {
// TODO: v2.8: remove variant fallback and make variant a required field
c.addMissingVariant()
variant, err := oid.FromString(c.AttestationVariant)
if err != nil {
return false
}
// make sure the variant is valid for the chosen CSP
switch variant {
case oid.AWSNitroTPM{}:
return c.Provider.AWS != nil
case oid.AzureSEVSNP{}, oid.AzureTrustedLaunch{}:
return c.Provider.Azure != nil
case oid.GCPSEVES{}:
return c.Provider.GCP != nil
case oid.QEMUVTPM{}:
return c.Provider.QEMU != nil
default:
return false
}
}
func (c *Config) addMissingVariant() {
if c.AttestationVariant != "" {
return
}
fmt.Fprintln(os.Stderr, "WARNING: the config key `attestationVariant` is not set. This key will be required in the next version.")
switch c.GetProvider() {
case cloudprovider.AWS:
c.AttestationVariant = oid.AWSNitroTPM{}.String()
case cloudprovider.Azure:
c.AttestationVariant = oid.AzureTrustedLaunch{}.String()
case cloudprovider.GCP:
c.AttestationVariant = oid.GCPSEVES{}.String()
case cloudprovider.QEMU:
c.AttestationVariant = oid.QEMUVTPM{}.String()
}
}

View File

@ -108,8 +108,6 @@ const (
ServiceBasePath = "/var/config" ServiceBasePath = "/var/config"
// MeasurementsFilename is the filename of CC measurements. // MeasurementsFilename is the filename of CC measurements.
MeasurementsFilename = "measurements" MeasurementsFilename = "measurements"
// EnforcedPCRsFilename is the filename for a list PCRs that are required to pass attestation.
EnforcedPCRsFilename = "enforcedPCRs"
// MeasurementSaltFilename is the filename of the salt used in creation of the clusterID. // MeasurementSaltFilename is the filename of the salt used in creation of the clusterID.
MeasurementSaltFilename = "measurementSalt" MeasurementSaltFilename = "measurementSalt"
// MeasurementSecretFilename is the filename of the secret used in creation of the clusterID. // MeasurementSecretFilename is the filename of the secret used in creation of the clusterID.
@ -118,8 +116,6 @@ const (
IDKeyDigestFilename = "idkeydigests" IDKeyDigestFilename = "idkeydigests"
// EnforceIDKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not. // EnforceIDKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
EnforceIDKeyDigestFilename = "enforceIdKeyDigest" EnforceIDKeyDigestFilename = "enforceIdKeyDigest"
// AzureCVM is the name of the file indicating whether the cluster is expected to run on CVMs or not.
AzureCVM = "azureCVM"
// K8sVersionFieldName is the name of the of the key holding the wanted Kubernetes version. // K8sVersionFieldName is the name of the of the key holding the wanted Kubernetes version.
K8sVersionFieldName = "cluster-version" K8sVersionFieldName = "cluster-version"

View File

@ -11,17 +11,13 @@ go_library(
visibility = ["//:__subpackages__"], visibility = ["//:__subpackages__"],
deps = [ deps = [
"//internal/atls", "//internal/atls",
"//internal/attestation/aws", "//internal/attestation/choose",
"//internal/attestation/azure/snp",
"//internal/attestation/azure/trustedlaunch",
"//internal/attestation/gcp",
"//internal/attestation/idkeydigest", "//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/oid",
"@com_github_fsnotify_fsnotify//:fsnotify", "@com_github_fsnotify_fsnotify//:fsnotify",
"@org_uber_go_zap//:zap", "@org_uber_go_zap//:zap",
], ],
@ -38,9 +34,11 @@ go_test(
"//internal/atls", "//internal/atls",
"//internal/attestation/idkeydigest", "//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/oid",
"@com_github_fsnotify_fsnotify//:fsnotify", "@com_github_fsnotify_fsnotify//:fsnotify",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View File

@ -9,74 +9,36 @@ package watcher
import ( import (
"encoding/asn1" "encoding/asn1"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"sync" "sync"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws" "github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"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/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
) )
// Updatable implements an updatable atls.Validator. // Updatable implements an updatable atls.Validator.
type Updatable struct { type Updatable struct {
log *logger.Logger log *logger.Logger
mux sync.Mutex mux sync.Mutex
newValidator newValidatorFunc fileHandler file.Handler
fileHandler file.Handler variant oid.Getter
csp cloudprovider.Provider
azureCVM bool
atls.Validator atls.Validator
} }
// NewValidator initializes a new updatable validator. // NewValidator initializes a new updatable validator.
func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler, azureCVM bool) (*Updatable, error) { func NewValidator(log *logger.Logger, variant oid.Getter, fileHandler file.Handler) (*Updatable, error) {
var newValidator newValidatorFunc
switch cloudprovider.FromString(csp) {
case cloudprovider.AWS:
newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator {
return aws.NewValidator(m, log)
}
case cloudprovider.Azure:
if azureCVM {
newValidator = func(m measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator {
return snp.NewValidator(m, idkeydigest, enforceIdKeyDigest, log)
}
} else {
newValidator = func(m measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator {
return trustedlaunch.NewValidator(m, log)
}
}
case cloudprovider.GCP:
newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator {
return gcp.NewValidator(m, log)
}
case cloudprovider.QEMU:
newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator {
return qemu.NewValidator(m, log)
}
default:
return nil, fmt.Errorf("unknown cloud service provider: %q", csp)
}
u := &Updatable{ u := &Updatable{
log: log, log: log,
newValidator: newValidator, fileHandler: fileHandler,
fileHandler: fileHandler, variant: variant,
csp: cloudprovider.FromString(csp),
azureCVM: azureCVM,
} }
if err := u.Update(); err != nil { if err := u.Update(); err != nil {
@ -112,22 +74,9 @@ func (u *Updatable) Update() error {
} }
u.log.Debugf("New measurements: %+v", measurements) u.log.Debugf("New measurements: %+v", measurements)
// handle legacy measurement format, where expected measurements and enforced measurements were stored in separate data structures
// TODO: remove with v2.4.0
var enforced []uint32
if err := u.fileHandler.ReadJSON(filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename), &enforced); err == nil {
u.log.Debugf("Detected legacy format. Loading enforced PCRs...")
if err := measurements.SetEnforced(enforced); err != nil {
return err
}
u.log.Debugf("Merged measurements with enforced values: %+v", measurements)
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
var digest idkeydigest.IDKeyDigests var digest idkeydigest.IDKeyDigests
var enforceIDKeyDigest bool var enforceIDKeyDigest bool
if u.csp == cloudprovider.Azure && u.azureCVM { if u.variant.OID().Equal(oid.AzureSEVSNP{}.OID()) {
u.log.Infof("Updating encforceIdKeyDigest value") u.log.Infof("Updating encforceIdKeyDigest value")
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename)) enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename))
if err != nil { if err != nil {
@ -150,9 +99,11 @@ func (u *Updatable) Update() error {
u.log.Debugf("New idkeydigest: %v", digest) u.log.Debugf("New idkeydigest: %v", digest)
} }
u.Validator = u.newValidator(measurements, digest, enforceIDKeyDigest, u.log) validator, err := choose.Validator(u.variant, measurements, digest, enforceIDKeyDigest, u.log)
if err != nil {
return fmt.Errorf("updating validator: %w", err)
}
u.Validator = validator
return nil return nil
} }
type newValidatorFunc func(measurements measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator

View File

@ -7,11 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
package watcher package watcher
import ( import (
"bytes"
"context" "context"
"encoding/asn1" "encoding/asn1"
"encoding/json" "encoding/json"
"errors"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -22,9 +20,11 @@ import (
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -40,29 +40,29 @@ func TestMain(m *testing.M) {
func TestNewUpdateableValidator(t *testing.T) { func TestNewUpdateableValidator(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
provider string variant oid.Getter
writeFile bool writeFile bool
wantErr bool wantErr bool
}{ }{
"azure": { "azure": {
provider: "azure", variant: oid.AzureSEVSNP{},
writeFile: true, writeFile: true,
}, },
"gcp": { "gcp": {
provider: "gcp", variant: oid.GCPSEVES{},
writeFile: true, writeFile: true,
}, },
"qemu": { "qemu": {
provider: "qemu", variant: oid.QEMUVTPM{},
writeFile: true, writeFile: true,
}, },
"no file": { "no file": {
provider: "azure", variant: oid.AzureSEVSNP{},
writeFile: false, writeFile: false,
wantErr: true, wantErr: true,
}, },
"invalid provider": { "invalid provider": {
provider: "invalid", variant: fakeOID{1, 3, 9900, 9999, 9999},
writeFile: true, writeFile: true,
wantErr: true, wantErr: true,
}, },
@ -77,33 +77,24 @@ func TestNewUpdateableValidator(t *testing.T) {
if tc.writeFile { if tc.writeFile {
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
map[uint32][]byte{ measurements.M{11: measurements.WithAllBytes(0x00, false)},
11: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
},
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
)) ))
keyDigest, err := json.Marshal(idkeydigest.DefaultsFor(cloudprovider.Azure))
require.NoError(err)
require.NoError(handler.Write( require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename), filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
[]byte{}, keyDigest,
)) ))
require.NoError(handler.Write( require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename), filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"), []byte("false"),
)) ))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
} }
_, err := NewValidator( _, err := NewValidator(
logger.NewTest(t), logger.NewTest(t),
tc.provider, tc.variant,
handler, handler,
false,
) )
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -118,26 +109,13 @@ func TestUpdate(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
// we need safe access for overwriting the fake validator OID
oid := fakeOID{1, 3, 9900, 1}
var oidLock sync.Mutex
updatedOID := func(newOID fakeOID) {
oidLock.Lock()
defer oidLock.Unlock()
oid = newOID
}
newValidator := func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
oidLock.Lock()
defer oidLock.Unlock()
return fakeValidator{fakeOID: oid}
}
handler := file.NewHandler(afero.NewMemMapFs()) handler := file.NewHandler(afero.NewMemMapFs())
// create server // create server
validator := &Updatable{ validator := &Updatable{
log: logger.NewTest(t), log: logger.NewTest(t),
newValidator: newValidator, variant: oid.Dummy{},
fileHandler: handler, fileHandler: handler,
} }
// Update should fail if the file does not exist // Update should fail if the file does not exist
@ -156,10 +134,6 @@ func TestUpdate(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename), filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"), []byte("false"),
)) ))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
// call update once to initialize the server's validator // call update once to initialize the server's validator
require.NoError(validator.Update()) require.NoError(validator.Update())
@ -175,7 +149,7 @@ func TestUpdate(t *testing.T) {
defer server.Close() defer server.Close()
// test connection to server // test connection to server
clientOID := fakeOID{1, 3, 9900, 1} clientOID := oid.Dummy{}
resp, err := testConnection(require, server.URL, clientOID) resp, err := testConnection(require, server.URL, clientOID)
require.NoError(err) require.NoError(err)
defer resp.Body.Close() defer resp.Body.Close()
@ -184,7 +158,7 @@ func TestUpdate(t *testing.T) {
assert.EqualValues("hello", body) assert.EqualValues("hello", body)
// update the server's validator // update the server's validator
updatedOID(fakeOID{1, 3, 9900, 2}) validator.variant = oid.QEMUVTPM{}
require.NoError(validator.Update()) require.NoError(validator.Update())
// client connection should fail now, since the server's validator expects a different OID from the client // client connection should fail now, since the server's validator expects a different OID from the client
@ -193,23 +167,6 @@ func TestUpdate(t *testing.T) {
defer resp.Body.Close() defer resp.Body.Close()
} }
assert.Error(err) assert.Error(err)
// update should work for legacy measurement format
// TODO: remove with v2.4.0
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
map[uint32][]byte{
11: bytes.Repeat([]byte{0x0}, 32),
12: bytes.Repeat([]byte{0x1}, 32),
},
file.OptOverwrite,
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
assert.NoError(validator.Update())
} }
func TestOIDConcurrency(t *testing.T) { func TestOIDConcurrency(t *testing.T) {
@ -226,14 +183,11 @@ func TestOIDConcurrency(t *testing.T) {
[]byte{}, []byte{},
)) ))
newValidator := func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}}
}
// create server // create server
validator := &Updatable{ validator := &Updatable{
log: logger.NewTest(t), log: logger.NewTest(t),
newValidator: newValidator, variant: oid.Dummy{},
fileHandler: handler, fileHandler: handler,
} }
// call update once to initialize the server's validator // call update once to initialize the server's validator
@ -262,21 +216,13 @@ func TestUpdateConcurrency(t *testing.T) {
validator := &Updatable{ validator := &Updatable{
log: logger.NewTest(t), log: logger.NewTest(t),
fileHandler: handler, fileHandler: handler,
newValidator: func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { variant: oid.Dummy{},
return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}}
},
} }
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
map[uint32][]byte{ measurements.M{11: measurements.WithAllBytes(0x00, false)},
11: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
},
file.OptNone, file.OptNone,
)) ))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
require.NoError(handler.Write( require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename), filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
[]byte{}, []byte{},
@ -285,10 +231,6 @@ func TestUpdateConcurrency(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename), filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"), []byte("false"),
)) ))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
var wg sync.WaitGroup var wg sync.WaitGroup
@ -303,8 +245,8 @@ func TestUpdateConcurrency(t *testing.T) {
wg.Wait() wg.Wait()
} }
func testConnection(require *require.Assertions, url string, oid fakeOID) (*http.Response, error) { func testConnection(require *require.Assertions, url string, oid oid.Getter) (*http.Response, error) {
clientConfig, err := atls.CreateAttestationClientTLSConfig(fakeIssuer{fakeOID: oid}, nil) clientConfig, err := atls.CreateAttestationClientTLSConfig(fakeIssuer{oid}, nil)
require.NoError(err) require.NoError(err)
client := http.Client{Transport: &http.Transport{TLSClientConfig: clientConfig}} client := http.Client{Transport: &http.Transport{TLSClientConfig: clientConfig}}
@ -314,29 +256,13 @@ func testConnection(require *require.Assertions, url string, oid fakeOID) (*http
} }
type fakeIssuer struct { type fakeIssuer struct {
fakeOID oid.Getter
} }
func (fakeIssuer) Issue(userData []byte, nonce []byte) ([]byte, error) { func (fakeIssuer) Issue(userData []byte, nonce []byte) ([]byte, error) {
return json.Marshal(fakeDoc{UserData: userData, Nonce: nonce}) return json.Marshal(fakeDoc{UserData: userData, Nonce: nonce})
} }
type fakeValidator struct {
fakeOID
err error
}
func (v fakeValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
var doc fakeDoc
if err := json.Unmarshal(attDoc, &doc); err != nil {
return nil, err
}
if !bytes.Equal(doc.Nonce, nonce) {
return nil, errors.New("invalid nonce")
}
return doc.UserData, v.err
}
type fakeOID asn1.ObjectIdentifier type fakeOID asn1.ObjectIdentifier
func (o fakeOID) OID() asn1.ObjectIdentifier { func (o fakeOID) OID() asn1.ObjectIdentifier {

View File

@ -18,6 +18,7 @@ go_library(
"//internal/file", "//internal/file",
"//internal/grpc/atlscredentials", "//internal/grpc/atlscredentials",
"//internal/logger", "//internal/logger",
"//internal/oid",
"//internal/watcher", "//internal/watcher",
"//joinservice/internal/kms", "//joinservice/internal/kms",
"//joinservice/internal/kubeadm", "//joinservice/internal/kubeadm",

View File

@ -27,6 +27,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials" "github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/watcher" "github.com/edgelesssys/constellation/v2/internal/watcher"
"github.com/edgelesssys/constellation/v2/joinservice/internal/kms" "github.com/edgelesssys/constellation/v2/joinservice/internal/kms"
"github.com/edgelesssys/constellation/v2/joinservice/internal/kubeadm" "github.com/edgelesssys/constellation/v2/joinservice/internal/kubeadm"
@ -42,25 +43,24 @@ const vpcIPTimeout = 30 * time.Second
func main() { func main() {
provider := flag.String("cloud-provider", "", "cloud service provider this binary is running on") provider := flag.String("cloud-provider", "", "cloud service provider this binary is running on")
keyServiceEndpoint := flag.String("key-service-endpoint", "", "endpoint of Constellations key management service") keyServiceEndpoint := flag.String("key-service-endpoint", "", "endpoint of Constellations key management service")
attestationVariant := flag.String("attestation-variant", "", "attestation variant to use for aTLS connections")
verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription) verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription)
flag.Parse() flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo()), zap.String("cloudProvider", *provider)). log.With(
Infof("Constellation Node Join Service") zap.String("version", constants.VersionInfo()),
zap.String("cloudProvider", *provider),
zap.String("attestationVariant", *attestationVariant),
).Infof("Constellation Node Join Service")
handler := file.NewHandler(afero.NewOsFs()) handler := file.NewHandler(afero.NewOsFs())
cvmRaw, err := handler.Read(filepath.Join(constants.ServiceBasePath, constants.AzureCVM)) variant, err := oid.FromString(*attestationVariant)
if err != nil { if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to get azureCVM from config map") log.With(zap.Error(err)).Fatalf("Failed to parse attestation variant")
} }
azureCVM, err := strconv.ParseBool(string(cvmRaw)) validator, err := watcher.NewValidator(log.Named("validator"), variant, handler)
if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to parse content of AzureCVM: %s", cvmRaw)
}
validator, err := watcher.NewValidator(log.Named("validator"), *provider, handler, azureCVM)
if err != nil { if err != nil {
flag.Usage() flag.Usage()
log.With(zap.Error(err)).Fatalf("Failed to create validator") log.With(zap.Error(err)).Fatalf("Failed to create validator")

View File

@ -6,13 +6,10 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/verify/cmd", importpath = "github.com/edgelesssys/constellation/v2/verify/cmd",
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
"//internal/attestation/aws", "//internal/attestation/choose",
"//internal/attestation/azure/snp",
"//internal/attestation/gcp",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider",
"//internal/constants", "//internal/constants",
"//internal/logger", "//internal/logger",
"//internal/oid",
"//verify/server", "//verify/server",
"@org_uber_go_zap//:zap", "@org_uber_go_zap//:zap",
], ],

View File

@ -11,39 +11,31 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws" "github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/verify/server" "github.com/edgelesssys/constellation/v2/verify/server"
"go.uber.org/zap" "go.uber.org/zap"
) )
func main() { func main() {
provider := flag.String("cloud-provider", "", "cloud service provider this binary is running on") attestationVariant := flag.String("attestation-variant", "", "attestation variant to use for aTLS connections")
verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription) verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription)
flag.Parse() flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo()), zap.String("cloudProvider", *provider)). log.With(zap.String("version", constants.VersionInfo()), zap.String("attestationVariant", *attestationVariant)).
Infof("Constellation Verification Service") Infof("Constellation Verification Service")
var issuer server.AttestationIssuer variant, err := oid.FromString(*attestationVariant)
switch cloudprovider.FromString(*provider) { if err != nil {
case cloudprovider.AWS: log.With(zap.Error(err)).Fatalf("Failed to parse attestation variant")
issuer = aws.NewIssuer(log) }
case cloudprovider.GCP: issuer, err := choose.Issuer(variant, log.Named("issuer"))
issuer = gcp.NewIssuer(log) if err != nil {
case cloudprovider.Azure: log.With(zap.Error(err)).Fatalf("Failed to create issuer")
issuer = snp.NewIssuer(log) // TODO: dynamic selection
case cloudprovider.QEMU:
issuer = qemu.NewIssuer(log)
default:
log.With(zap.String("cloudProvider", *provider)).Fatalf("Unknown cloud provider")
} }
server := server.New(log.Named("server"), issuer) server := server.New(log.Named("server"), issuer)