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.
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, error) {
return []byte{}, nil

View File

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

View File

@ -29,7 +29,6 @@ import (
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/diskencryption"
"github.com/edgelesssys/constellation/v2/internal/atls"
"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/file"
"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)
}
// Check if we are running on a CVM
_, isCVM := s.issuer.(*snp.Issuer)
clusterName := req.ClusterName
if clusterName == "" {
clusterName = "constellation"
@ -183,7 +179,6 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
measurementSalt,
req.EnforcedPcrs,
req.EnforceIdkeydigest,
isCVM,
req.HelmDeployments,
req.ConformanceMode,
components.NewComponentsFromInitProto(req.KubernetesComponents),
@ -260,7 +255,6 @@ type ClusterInitializer interface {
measurementSalt []byte,
enforcedPcrs []uint32,
enforceIDKeyDigest bool,
azureCVM bool,
helmDeployments []byte,
conformanceMode bool,
kubernetesComponents components.Components,

View File

@ -320,7 +320,7 @@ type stubClusterInitializer struct {
}
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, error) {
return i.initClusterKubeconfig, i.initClusterErr

View File

@ -15,7 +15,6 @@ import (
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
@ -82,7 +81,7 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
// InitCluster initializes a new Kubernetes cluster and applies pod network provider.
func (k *KubeWrapper) InitCluster(
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,
) ([]byte, error) {
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)
}
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)
}
@ -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.
func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context, azureCVM string) error {
func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context) error {
config := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
@ -329,9 +328,7 @@ func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context, azureCVM strin
Name: constants.InternalConfigMap,
Namespace: "kube-system",
},
Data: map[string]string{
constants.AzureCVM: azureCVM,
},
Data: map[string]string{},
}
// 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(
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 {

View File

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

View File

@ -14,46 +14,40 @@ import (
"fmt"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws"
"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/choose"
"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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/cobra"
)
// Validator validates Platform Configuration Registers (PCRs).
type Validator struct {
provider cloudprovider.Provider
attestationVariant oid.Getter
pcrs measurements.M
idkeydigests idkeydigest.IDKeyDigests
enforceIDKeyDigest bool
azureCVM bool
validator atls.Validator
log debugLog
}
// 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}
if provider == cloudprovider.Unknown {
return nil, errors.New("unknown cloud provider")
variant, err := oid.FromString(conf.AttestationVariant)
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 {
return nil, err
}
if v.provider == cloudprovider.Azure {
v.azureCVM = *conf.Provider.Azure.ConfidentialVM
if v.azureCVM {
v.enforceIDKeyDigest = *conf.Provider.Azure.EnforceIDKeyDigest
v.idkeydigests = conf.Provider.Azure.IDKeyDigest
}
if v.attestationVariant.OID().Equal(oid.AzureSEVSNP{}.OID()) {
v.enforceIDKeyDigest = conf.EnforcesIDKeyDigest()
v.idkeydigests = conf.IDKeyDigests()
}
return &v, nil
@ -100,26 +94,26 @@ func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
}
func (v *Validator) setPCRs(config *config.Config) error {
switch v.provider {
case cloudprovider.AWS:
switch v.attestationVariant {
case oid.AWSNitroTPM{}:
awsPCRs := config.Provider.AWS.Measurements
if len(awsPCRs) == 0 {
return errors.New("no expected measurement provided")
}
v.pcrs = awsPCRs
case cloudprovider.Azure:
case oid.AzureSEVSNP{}, oid.AzureTrustedLaunch{}:
azurePCRs := config.Provider.Azure.Measurements
if len(azurePCRs) == 0 {
return errors.New("no expected measurement provided")
}
v.pcrs = azurePCRs
case cloudprovider.GCP:
case oid.GCPSEVES{}:
gcpPCRs := config.Provider.GCP.Measurements
if len(gcpPCRs) == 0 {
return errors.New("no expected measurement provided")
}
v.pcrs = gcpPCRs
case cloudprovider.QEMU:
case oid.QEMUVTPM{}:
qemuPCRs := config.Provider.QEMU.Measurements
if len(qemuPCRs) == 0 {
return errors.New("no expected measurement provided")
@ -142,20 +136,9 @@ func (v *Validator) PCRS() measurements.M {
func (v *Validator) updateValidator(cmd *cobra.Command) {
log := warnLogger{cmd: cmd, log: v.log}
switch v.provider {
case cloudprovider.GCP:
v.validator = gcp.NewValidator(v.pcrs, 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)
}
// Use of a valid variant has been check in NewValidator so we may drop the error
v.validator, _ = choose.Validator(v.attestationVariant, v.pcrs, v.idkeydigests, v.enforceIDKeyDigest, log)
}
// 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/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/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewValidator(t *testing.T) {
@ -37,47 +38,82 @@ func TestNewValidator(t *testing.T) {
}
testCases := map[string]struct {
provider cloudprovider.Provider
config *config.Config
pcrs measurements.M
enforceIDKeyDigest bool
digest idkeydigest.IDKeyDigests
azureCVM bool
wantErr bool
}{
"gcp": {
provider: cloudprovider.GCP,
pcrs: testPCRs,
config: &config.Config{
AttestationVariant: oid.GCPSEVES{}.String(),
Provider: config.ProviderConfig{
GCP: &config.GCPConfig{
Measurements: testPCRs,
},
},
},
},
"azure cvm": {
provider: cloudprovider.Azure,
pcrs: testPCRs,
azureCVM: true,
config: &config.Config{
AttestationVariant: oid.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
},
"azure trusted launch": {
provider: cloudprovider.Azure,
pcrs: testPCRs,
azureCVM: false,
config: &config.Config{
AttestationVariant: oid.AzureTrustedLaunch{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
},
"qemu": {
provider: cloudprovider.QEMU,
pcrs: testPCRs,
config: &config.Config{
AttestationVariant: oid.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
},
"no pcrs provided": {
provider: cloudprovider.Azure,
pcrs: measurements.M{},
config: &config.Config{
AttestationVariant: oid.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: measurements.M{},
},
},
},
wantErr: true,
},
"unknown provider": {
provider: cloudprovider.Unknown,
pcrs: testPCRs,
"unknown variant": {
config: &config.Config{
AttestationVariant: "unknown",
Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
wantErr: true,
},
"set idkeydigest": {
provider: cloudprovider.Azure,
pcrs: testPCRs,
digest: idkeydigest.IDKeyDigests{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
enforceIDKeyDigest: true,
config: &config.Config{
AttestationVariant: oid.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
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) {
assert := assert.New(t)
conf := &config.Config{Provider: config.ProviderConfig{}}
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))
validators, err := NewValidator(tc.config, logger.NewTest(t))
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.pcrs, validators.pcrs)
assert.Equal(tc.provider, validators.provider)
assert.Equal(tc.config.GetMeasurements(), validators.pcrs)
variant, err := oid.FromString(tc.config.AttestationVariant)
require.NoError(t, err)
assert.Equal(variant, validators.attestationVariant)
}
})
}
@ -129,29 +156,27 @@ func TestValidatorV(t *testing.T) {
}
testCases := map[string]struct {
provider cloudprovider.Provider
variant oid.Getter
pcrs measurements.M
wantVs atls.Validator
azureCVM bool
}{
"gcp": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
wantVs: gcp.NewValidator(newTestPCRs(), nil),
},
"azure cvm": {
provider: cloudprovider.Azure,
variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(),
wantVs: snp.NewValidator(newTestPCRs(), idkeydigest.IDKeyDigests{}, false, nil),
azureCVM: true,
},
"azure trusted launch": {
provider: cloudprovider.Azure,
variant: oid.AzureTrustedLaunch{},
pcrs: newTestPCRs(),
wantVs: trustedlaunch.NewValidator(newTestPCRs(), nil),
},
"qemu": {
provider: cloudprovider.QEMU,
variant: oid.QEMUVTPM{},
pcrs: newTestPCRs(),
wantVs: qemu.NewValidator(newTestPCRs(), nil),
},
@ -161,7 +186,7 @@ func TestValidatorV(t *testing.T) {
t.Run(name, func(t *testing.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{})
@ -206,50 +231,50 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
}
testCases := map[string]struct {
provider cloudprovider.Provider
variant oid.Getter
pcrs measurements.M
ownerID string
clusterID string
wantErr bool
}{
"gcp update owner ID": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
ownerID: one64,
},
"gcp update cluster ID": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
clusterID: one64,
},
"gcp update both": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
ownerID: one64,
clusterID: one64,
},
"azure update owner ID": {
provider: cloudprovider.Azure,
variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(),
ownerID: one64,
},
"azure update cluster ID": {
provider: cloudprovider.Azure,
variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(),
clusterID: one64,
},
"azure update both": {
provider: cloudprovider.Azure,
variant: oid.AzureSEVSNP{},
pcrs: newTestPCRs(),
ownerID: one64,
clusterID: one64,
},
"owner ID and cluster ID empty": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
},
"invalid encoding": {
provider: cloudprovider.GCP,
variant: oid.GCPSEVES{},
pcrs: newTestPCRs(),
ownerID: "invalid",
wantErr: true,
@ -260,7 +285,7 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
t.Run(name, func(t *testing.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)
@ -392,7 +417,7 @@ func TestUpdatePCR(t *testing.T) {
}
validators := &Validator{
provider: cloudprovider.GCP,
attestationVariant: oid.GCPSEVES{},
pcrs: pcrs,
}
err := validators.updatePCR(tc.pcrIndex, tc.encoded)

View File

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

View File

@ -15,6 +15,7 @@ import (
"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/oid"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/spf13/afero"
@ -105,6 +106,18 @@ func createConfig(provider cloudprovider.Provider) *config.Config {
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
}

View File

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@ -92,6 +93,9 @@ func TestConfigGenerateDefaultGCPSpecific(t *testing.T) {
cg := &configGenerateCmd{log: logger.NewTest(t)}
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
err := fileHandler.ReadYAML(constants.ConfigFilename, &readConfig)
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)
}
i.log.Debugf("Checked license")
validator, err := cloudcmd.NewValidator(provider, conf, i.log)
validator, err := cloudcmd.NewValidator(conf, i.log)
if err != nil {
return err
}

View File

@ -436,6 +436,7 @@ func TestAttestation(t *testing.T) {
cfg := config.Default()
cfg.Image = "image"
cfg.AttestationVariant = oid.QEMUVTPM{}.String()
cfg.RemoveProviderExcept(cloudprovider.QEMU)
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, 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 {
case cloudprovider.Azure:
conf.AttestationVariant = oid.AzureSEVSNP{}.String()
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
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[12] = measurements.WithAllBytes(0xcc, false)
case cloudprovider.GCP:
conf.AttestationVariant = oid.GCPSEVES{}.String()
conf.Provider.GCP.Region = "test-region"
conf.Provider.GCP.Project = "test-project"
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[12] = measurements.WithAllBytes(0xcc, false)
case cloudprovider.QEMU:
conf.AttestationVariant = oid.QEMUVTPM{}.String()
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, false)
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, 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
}
validator, err := cloudcmd.NewValidator(provider, conf, r.log)
validator, err := cloudcmd.NewValidator(conf, r.log)
if err != nil {
return err
}

View File

@ -86,7 +86,7 @@ func (v *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
provider := conf.GetProvider()
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 {
return err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -415,7 +415,6 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
"image": i.autoscalerImage,
},
"verification-service": map[string]any{
"csp": i.csp.String(),
"image": i.verificationServiceImage,
},
"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["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()
switch csp {
case cloudprovider.Azure:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ go_library(
"//internal/config/instancetypes",
"//internal/constants",
"//internal/file",
"//internal/oid",
"//internal/versions",
"//internal/versionsapi",
"@com_github_go_playground_locales//en",
@ -48,6 +49,7 @@ go_test(
"//internal/config/instancetypes",
"//internal/constants",
"//internal/file",
"//internal/oid",
"@com_github_go_playground_locales//en",
"@com_github_go_playground_universal_translator//:universal-translator",
"@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
DebugCluster *bool `yaml:"debugCluster" validate:"required"`
// 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.
Provider ProviderConfig `yaml:"provider" validate:"dive"`
// description: |
@ -573,10 +576,15 @@ func (c *Config) Validate(force bool) error {
if err := validate.RegisterTranslation("version_compatibility", trans, registerVersionCompatibilityError, translateVersionCompatibilityError); err != nil {
return err
}
if err := validate.RegisterTranslation("valid_name", trans, registerValidateNameError, c.translateValidateNameError); err != nil {
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 {
return err
}
@ -613,6 +621,10 @@ func (c *Config) Validate(force bool) error {
return err
}
if err := validate.RegisterValidation("valid_attestation_variant", c.validAttestVariant); err != nil {
return err
}
// Register provider validation
validate.RegisterStructValidation(validateProvider, ProviderConfig{})

View File

@ -25,7 +25,7 @@ func init() {
ConfigDoc.Type = "Config"
ConfigDoc.Comments[encoder.LineComment] = "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].Type = "string"
ConfigDoc.Fields[0].Note = ""
@ -61,18 +61,23 @@ func init() {
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].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].Type = "ProviderConfig"
ConfigDoc.Fields[7].Note = ""
ConfigDoc.Fields[7].Description = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[8].Name = "upgrade"
ConfigDoc.Fields[8].Type = "UpgradeConfig"
ConfigDoc.Fields[7].Name = "attestationVariant"
ConfigDoc.Fields[7].Type = "string"
ConfigDoc.Fields[7].Note = "TODO: v2.8: Mark required\n"
ConfigDoc.Fields[7].Description = "Attestation variant used to verify the integrity of a node."
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Attestation variant used to verify the integrity of a node."
ConfigDoc.Fields[8].Name = "provider"
ConfigDoc.Fields[8].Type = "ProviderConfig"
ConfigDoc.Fields[8].Note = ""
ConfigDoc.Fields[8].Description = "Configuration to apply during constellation upgrade."
ConfigDoc.Fields[8].Comments[encoder.LineComment] = "Configuration to apply during constellation upgrade."
ConfigDoc.Fields[8].Description = "Supported cloud providers and their specific configurations."
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.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/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
@ -122,6 +123,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default()
c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = oid.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus"
@ -141,6 +143,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default()
c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = oid.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus"
@ -232,6 +235,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config {
cnf := Default()
cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = oid.AzureSEVSNP{}.String()
az := cnf.Provider.Azure
az.SubscriptionID = "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 := Default()
cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = oid.GCPSEVES{}.String()
gcp := cnf.Provider.GCP
gcp.Region = "test-region"
gcp.Project = "test-project"

View File

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"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/versionsapi"
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
}
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"
// MeasurementsFilename is the filename of CC 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 = "measurementSalt"
// MeasurementSecretFilename is the filename of the secret used in creation of the clusterID.
@ -118,8 +116,6 @@ const (
IDKeyDigestFilename = "idkeydigests"
// EnforceIDKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
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 = "cluster-version"

View File

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

View File

@ -9,74 +9,36 @@ package watcher
import (
"encoding/asn1"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws"
"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/choose"
"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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
)
// Updatable implements an updatable atls.Validator.
type Updatable struct {
log *logger.Logger
mux sync.Mutex
newValidator newValidatorFunc
fileHandler file.Handler
csp cloudprovider.Provider
azureCVM bool
variant oid.Getter
atls.Validator
}
// NewValidator initializes a new updatable validator.
func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler, azureCVM bool) (*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)
}
func NewValidator(log *logger.Logger, variant oid.Getter, fileHandler file.Handler) (*Updatable, error) {
u := &Updatable{
log: log,
newValidator: newValidator,
fileHandler: fileHandler,
csp: cloudprovider.FromString(csp),
azureCVM: azureCVM,
variant: variant,
}
if err := u.Update(); err != nil {
@ -112,22 +74,9 @@ func (u *Updatable) Update() error {
}
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 enforceIDKeyDigest bool
if u.csp == cloudprovider.Azure && u.azureCVM {
if u.variant.OID().Equal(oid.AzureSEVSNP{}.OID()) {
u.log.Infof("Updating encforceIdKeyDigest value")
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename))
if err != nil {
@ -150,9 +99,11 @@ func (u *Updatable) Update() error {
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
}
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
import (
"bytes"
"context"
"encoding/asn1"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
@ -22,9 +20,11 @@ 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/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -40,29 +40,29 @@ func TestMain(m *testing.M) {
func TestNewUpdateableValidator(t *testing.T) {
testCases := map[string]struct {
provider string
variant oid.Getter
writeFile bool
wantErr bool
}{
"azure": {
provider: "azure",
variant: oid.AzureSEVSNP{},
writeFile: true,
},
"gcp": {
provider: "gcp",
variant: oid.GCPSEVES{},
writeFile: true,
},
"qemu": {
provider: "qemu",
variant: oid.QEMUVTPM{},
writeFile: true,
},
"no file": {
provider: "azure",
variant: oid.AzureSEVSNP{},
writeFile: false,
wantErr: true,
},
"invalid provider": {
provider: "invalid",
variant: fakeOID{1, 3, 9900, 9999, 9999},
writeFile: true,
wantErr: true,
},
@ -77,33 +77,24 @@ func TestNewUpdateableValidator(t *testing.T) {
if tc.writeFile {
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
map[uint32][]byte{
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},
measurements.M{11: measurements.WithAllBytes(0x00, false)},
))
keyDigest, err := json.Marshal(idkeydigest.DefaultsFor(cloudprovider.Azure))
require.NoError(err)
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
[]byte{},
keyDigest,
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"),
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
}
_, err := NewValidator(
logger.NewTest(t),
tc.provider,
tc.variant,
handler,
false,
)
if tc.wantErr {
assert.Error(err)
@ -118,25 +109,12 @@ func TestUpdate(t *testing.T) {
assert := assert.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())
// create server
validator := &Updatable{
log: logger.NewTest(t),
newValidator: newValidator,
variant: oid.Dummy{},
fileHandler: handler,
}
@ -156,10 +134,6 @@ func TestUpdate(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"),
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
// call update once to initialize the server's validator
require.NoError(validator.Update())
@ -175,7 +149,7 @@ func TestUpdate(t *testing.T) {
defer server.Close()
// test connection to server
clientOID := fakeOID{1, 3, 9900, 1}
clientOID := oid.Dummy{}
resp, err := testConnection(require, server.URL, clientOID)
require.NoError(err)
defer resp.Body.Close()
@ -184,7 +158,7 @@ func TestUpdate(t *testing.T) {
assert.EqualValues("hello", body)
// update the server's validator
updatedOID(fakeOID{1, 3, 9900, 2})
validator.variant = oid.QEMUVTPM{}
require.NoError(validator.Update())
// 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()
}
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) {
@ -226,13 +183,10 @@ func TestOIDConcurrency(t *testing.T) {
[]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
validator := &Updatable{
log: logger.NewTest(t),
newValidator: newValidator,
variant: oid.Dummy{},
fileHandler: handler,
}
@ -262,21 +216,13 @@ func TestUpdateConcurrency(t *testing.T) {
validator := &Updatable{
log: logger.NewTest(t),
fileHandler: handler,
newValidator: func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}}
},
variant: oid.Dummy{},
}
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
map[uint32][]byte{
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},
},
measurements.M{11: measurements.WithAllBytes(0x00, false)},
file.OptNone,
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
[]byte{},
@ -285,10 +231,6 @@ func TestUpdateConcurrency(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"),
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
[]byte("true"),
))
var wg sync.WaitGroup
@ -303,8 +245,8 @@ func TestUpdateConcurrency(t *testing.T) {
wg.Wait()
}
func testConnection(require *require.Assertions, url string, oid fakeOID) (*http.Response, error) {
clientConfig, err := atls.CreateAttestationClientTLSConfig(fakeIssuer{fakeOID: oid}, nil)
func testConnection(require *require.Assertions, url string, oid oid.Getter) (*http.Response, error) {
clientConfig, err := atls.CreateAttestationClientTLSConfig(fakeIssuer{oid}, nil)
require.NoError(err)
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 {
fakeOID
oid.Getter
}
func (fakeIssuer) Issue(userData []byte, nonce []byte) ([]byte, error) {
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
func (o fakeOID) OID() asn1.ObjectIdentifier {

View File

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

View File

@ -27,6 +27,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
"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/joinservice/internal/kms"
"github.com/edgelesssys/constellation/v2/joinservice/internal/kubeadm"
@ -42,25 +43,24 @@ const vpcIPTimeout = 30 * time.Second
func main() {
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")
attestationVariant := flag.String("attestation-variant", "", "attestation variant to use for aTLS connections")
verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription)
flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo()), zap.String("cloudProvider", *provider)).
Infof("Constellation Node Join Service")
log.With(
zap.String("version", constants.VersionInfo()),
zap.String("cloudProvider", *provider),
zap.String("attestationVariant", *attestationVariant),
).Infof("Constellation Node Join Service")
handler := file.NewHandler(afero.NewOsFs())
cvmRaw, err := handler.Read(filepath.Join(constants.ServiceBasePath, constants.AzureCVM))
variant, err := oid.FromString(*attestationVariant)
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))
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)
validator, err := watcher.NewValidator(log.Named("validator"), variant, handler)
if err != nil {
flag.Usage()
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",
visibility = ["//visibility:private"],
deps = [
"//internal/attestation/aws",
"//internal/attestation/azure/snp",
"//internal/attestation/gcp",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider",
"//internal/attestation/choose",
"//internal/constants",
"//internal/logger",
"//internal/oid",
"//verify/server",
"@org_uber_go_zap//:zap",
],

View File

@ -11,39 +11,31 @@ import (
"net"
"strconv"
"github.com/edgelesssys/constellation/v2/internal/attestation/aws"
"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/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/verify/server"
"go.uber.org/zap"
)
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)
flag.Parse()
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")
var issuer server.AttestationIssuer
switch cloudprovider.FromString(*provider) {
case cloudprovider.AWS:
issuer = aws.NewIssuer(log)
case cloudprovider.GCP:
issuer = gcp.NewIssuer(log)
case cloudprovider.Azure:
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")
variant, err := oid.FromString(*attestationVariant)
if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to parse attestation variant")
}
issuer, err := choose.Issuer(variant, log.Named("issuer"))
if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to create issuer")
}
server := server.New(log.Named("server"), issuer)