AB#2350: Configurably enforce idkeydigest on Azure

* Add join-config entry for "enforceIdKeyDigest" bool
* Add join-config entry for "idkeydigest"
* Initially filled with TPM value from bootstrapper
* Add config entries for idkeydigest and enforceIdKeyDigest
* Extend azure attestation validator to check idkeydigest,
if configured.
* Update unittests
* Add logger to NewValidator for all CSPs
* Add csp to Updateable type

Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
Co-authored-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Otto Bittner 2022-08-29 16:41:09 +02:00
parent c84e44913b
commit 4adc19b7f5
31 changed files with 350 additions and 136 deletions

View File

@ -94,7 +94,7 @@ func main() {
}
clusterInitJoiner = kubernetes.New(
"gcp", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &gcpcloud.CloudControllerManager{},
&gcpcloud.CloudNodeManager{}, &gcpcloud.Autoscaler{}, metadata, pcrsJSON,
&gcpcloud.CloudNodeManager{}, &gcpcloud.Autoscaler{}, metadata, pcrsJSON, nil,
)
openTPM = vtpm.OpenVTPM
fs = afero.NewOsFs()
@ -108,6 +108,11 @@ func main() {
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
}
idKeyDigest, err := azure.GetIdKeyDigest(vtpm.OpenVTPM)
if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to get idkeydigest")
}
issuer = azure.NewIssuer()
metadata, err := azurecloud.NewMetadata(ctx)
@ -125,7 +130,7 @@ func main() {
}
clusterInitJoiner = kubernetes.New(
"azure", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata),
&azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata, pcrsJSON,
&azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata, pcrsJSON, idKeyDigest,
)
openTPM = vtpm.OpenVTPM
@ -146,7 +151,7 @@ func main() {
}
clusterInitJoiner = kubernetes.New(
"qemu", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &qemucloud.CloudControllerManager{},
&qemucloud.CloudNodeManager{}, &qemucloud.Autoscaler{}, metadata, pcrsJSON,
&qemucloud.CloudNodeManager{}, &qemucloud.Autoscaler{}, metadata, pcrsJSON, nil,
)
metadataAPI = metadata

View File

@ -15,7 +15,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,
context.Context, []string, string, string, []byte, []uint32, bool,
resources.KMSConfig, map[string]string, []byte, *logger.Logger,
) ([]byte, error) {
return []byte{}, nil

View File

@ -37,6 +37,7 @@ type InitRequest struct {
Salt []byte `protobuf:"bytes,10,opt,name=salt,proto3" json:"salt,omitempty"`
HelmDeployments []byte `protobuf:"bytes,11,opt,name=helm_deployments,json=helmDeployments,proto3" json:"helm_deployments,omitempty"`
EnforcedPcrs []uint32 `protobuf:"varint,12,rep,packed,name=enforced_pcrs,json=enforcedPcrs,proto3" json:"enforced_pcrs,omitempty"`
EnforceIdkeydigest bool `protobuf:"varint,13,opt,name=enforce_idkeydigest,json=enforceIdkeydigest,proto3" json:"enforce_idkeydigest,omitempty"`
}
func (x *InitRequest) Reset() {
@ -155,6 +156,13 @@ func (x *InitRequest) GetEnforcedPcrs() []uint32 {
return nil
}
func (x *InitRequest) GetEnforceIdkeydigest() bool {
if x != nil {
return x.EnforceIdkeydigest
}
return false
}
type InitResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -277,7 +285,7 @@ var File_init_proto protoreflect.FileDescriptor
var file_init_proto_rawDesc = []byte{
0x0a, 0x0a, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x69, 0x6e,
0x69, 0x74, 0x22, 0x85, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x69, 0x74, 0x22, 0xb6, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x61, 0x75, 0x74, 0x6f, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e,
0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x09, 0x52, 0x15, 0x61, 0x75, 0x74, 0x6f, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67,
@ -309,26 +317,29 @@ var file_init_proto_rawDesc = []byte{
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x68, 0x65, 0x6c, 0x6d, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65,
0x64, 0x5f, 0x70, 0x63, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x65, 0x6e,
0x66, 0x6f, 0x72, 0x63, 0x65, 0x64, 0x50, 0x63, 0x72, 0x73, 0x22, 0x68, 0x0a, 0x0c, 0x49, 0x6e,
0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75,
0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a,
0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77,
0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77,
0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74,
0x65, 0x72, 0x49, 0x64, 0x22, 0x47, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x55, 0x73, 0x65, 0x72, 0x4b,
0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d,
0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x32, 0x34, 0x0a,
0x03, 0x41, 0x50, 0x49, 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x69,
0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x12, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f,
0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6f, 0x6f, 0x74,
0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x66, 0x6f, 0x72, 0x63, 0x65, 0x64, 0x50, 0x63, 0x72, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x65, 0x6e,
0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x6b, 0x65, 0x79, 0x64, 0x69, 0x67, 0x65, 0x73,
0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65,
0x49, 0x64, 0x6b, 0x65, 0x79, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x0c, 0x49,
0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b,
0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f,
0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f,
0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73,
0x74, 0x65, 0x72, 0x49, 0x64, 0x22, 0x47, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x55, 0x73, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x32, 0x34,
0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e,
0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x12, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63,
0x6f, 0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6f, 0x6f,
0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -21,6 +21,7 @@ message InitRequest {
bytes salt = 10;
bytes helm_deployments = 11;
repeated uint32 enforced_pcrs = 12;
bool enforce_idkeydigest = 13;
}
message InitResponse {

View File

@ -120,6 +120,7 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
req.KubernetesVersion,
measurementSalt,
req.EnforcedPcrs,
req.EnforceIdkeydigest,
resources.KMSConfig{
MasterSecret: req.MasterSecret,
Salt: req.Salt,
@ -203,6 +204,7 @@ type ClusterInitializer interface {
k8sVersion string,
measurementSalt []byte,
enforcedPcrs []uint32,
enforceIdKeyDigest bool,
kmsConfig resources.KMSConfig,
sshUserKeys map[string]string,
helmDeployments []byte,

View File

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

View File

@ -3,6 +3,7 @@ package resources
import (
"fmt"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/kubernetes"
"github.com/edgelesssys/constellation/internal/versions"
@ -23,7 +24,16 @@ type joinServiceDaemonset struct {
}
// NewJoinServiceDaemonset returns a daemonset for the join service.
func NewJoinServiceDaemonset(csp, measurementsJSON, enforcedPCRsJSON string, measurementSalt []byte) *joinServiceDaemonset {
func NewJoinServiceDaemonset(csp, measurementsJSON, enforcedPCRsJSON, initialIdKeyDigest, enforceIdKeyDigest string, measurementSalt []byte) *joinServiceDaemonset {
joinConfigData := map[string]string{
constants.MeasurementsFilename: measurementsJSON,
constants.EnforcedPCRsFilename: enforcedPCRsJSON,
}
if cloudprovider.FromString(csp) == cloudprovider.Azure {
joinConfigData[constants.EnforceIdKeyDigestFilename] = enforceIdKeyDigest
joinConfigData[constants.IdKeyDigestFilename] = initialIdKeyDigest
}
return &joinServiceDaemonset{
ClusterRole: rbac.ClusterRole{
TypeMeta: meta.TypeMeta{
@ -240,10 +250,7 @@ func NewJoinServiceDaemonset(csp, measurementsJSON, enforcedPCRsJSON string, mea
Name: constants.JoinConfigMap,
Namespace: constants.ConstellationNamespace,
},
Data: map[string]string{
constants.MeasurementsFilename: measurementsJSON,
constants.EnforcedPCRsFilename: enforcedPCRsJSON,
},
Data: joinConfigData,
BinaryData: map[string][]byte{
constants.MeasurementSaltFilename: measurementSalt,
},

View File

@ -9,7 +9,7 @@ import (
)
func TestNewJoinServiceDaemonset(t *testing.T) {
deployment := NewJoinServiceDaemonset("csp", "measurementsJSON", "enforcedPCRsJSON", []byte{0x0, 0x1, 0x2})
deployment := NewJoinServiceDaemonset("csp", "measurementsJSON", "enforcedPCRsJSON", "deadbeef", "true", []byte{0x0, 0x1, 0x2})
deploymentYAML, err := deployment.Marshal()
require.NoError(t, err)

View File

@ -2,9 +2,11 @@ package kubernetes
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi"
@ -45,12 +47,13 @@ type KubeWrapper struct {
clusterAutoscaler ClusterAutoscaler
providerMetadata ProviderMetadata
initialMeasurementsJSON []byte
initialIdKeyDigest []byte
getIPAddr func() (string, error)
}
// New creates a new KubeWrapper with real values.
func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client, cloudControllerManager CloudControllerManager,
cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, providerMetadata ProviderMetadata, initialMeasurementsJSON []byte,
cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, providerMetadata ProviderMetadata, initialMeasurementsJSON, initialIdKeyDigest []byte,
) *KubeWrapper {
return &KubeWrapper{
cloudProvider: cloudProvider,
@ -63,6 +66,7 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
clusterAutoscaler: clusterAutoscaler,
providerMetadata: providerMetadata,
initialMeasurementsJSON: initialMeasurementsJSON,
initialIdKeyDigest: initialIdKeyDigest,
getIPAddr: getIPAddr,
}
}
@ -70,7 +74,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, autoscalingNodeGroups []string, cloudServiceAccountURI, versionString string, measurementSalt []byte,
enforcedPCRs []uint32, kmsConfig resources.KMSConfig, sshUsers map[string]string, helmDeployments []byte, log *logger.Logger,
enforcedPCRs []uint32, enforceIdKeyDigest bool, kmsConfig resources.KMSConfig, sshUsers map[string]string, helmDeployments []byte, log *logger.Logger,
) ([]byte, error) {
k8sVersion, err := versions.NewValidK8sVersion(versionString)
if err != nil {
@ -174,7 +178,7 @@ func (k *KubeWrapper) InitCluster(
return nil, fmt.Errorf("setting up kms: %w", err)
}
if err := k.setupJoinService(k.cloudProvider, k.initialMeasurementsJSON, measurementSalt, enforcedPCRs); err != nil {
if err := k.setupJoinService(k.cloudProvider, k.initialMeasurementsJSON, measurementSalt, enforcedPCRs, k.initialIdKeyDigest, enforceIdKeyDigest); err != nil {
return nil, fmt.Errorf("setting up join service failed: %w", err)
}
@ -305,7 +309,7 @@ func (k *KubeWrapper) GetKubeconfig() ([]byte, error) {
}
func (k *KubeWrapper) setupJoinService(
csp string, measurementsJSON, measurementSalt []byte, enforcedPCRs []uint32,
csp string, measurementsJSON, measurementSalt []byte, enforcedPCRs []uint32, initialIdKeyDigest []byte, enforceIdKeyDigest bool,
) error {
enforcedPCRsJSON, err := json.Marshal(enforcedPCRs)
if err != nil {
@ -313,7 +317,7 @@ func (k *KubeWrapper) setupJoinService(
}
joinConfiguration := resources.NewJoinServiceDaemonset(
csp, string(measurementsJSON), string(enforcedPCRsJSON), measurementSalt,
csp, string(measurementsJSON), string(enforcedPCRsJSON), hex.EncodeToString(initialIdKeyDigest), strconv.FormatBool(enforceIdKeyDigest), measurementSalt,
)
return k.clusterUtil.SetupJoinService(k.client, joinConfiguration)

View File

@ -302,7 +302,7 @@ func TestInitCluster(t *testing.T) {
_, err := kube.InitCluster(
context.Background(), autoscalingNodeGroups, serviceAccountURI, string(tc.k8sVersion),
nil, nil, resources.KMSConfig{MasterSecret: masterSecret}, nil, nil, logger.NewTest(t),
nil, nil, false, resources.KMSConfig{MasterSecret: masterSecret}, nil, nil, logger.NewTest(t),
)
if tc.wantErr {

View File

@ -3,6 +3,7 @@ package cloudcmd
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
@ -17,10 +18,12 @@ import (
)
type Validator struct {
provider cloudprovider.Provider
pcrs map[uint32][]byte
enforcedPCRs []uint32
validator atls.Validator
provider cloudprovider.Provider
pcrs map[uint32][]byte
enforcedPCRs []uint32
idkeydigest []byte
enforceIdKeyDigest bool
validator atls.Validator
}
func NewValidator(provider cloudprovider.Provider, config *config.Config) (*Validator, error) {
@ -32,6 +35,16 @@ func NewValidator(provider cloudprovider.Provider, config *config.Config) (*Vali
if err := v.setPCRs(config); err != nil {
return nil, err
}
if v.provider == cloudprovider.Azure {
idkeydigest, err := hex.DecodeString(config.Provider.Azure.IdKeyDigest)
if err != nil {
return nil, fmt.Errorf("bad config: decoding idkeydigest from config: %w", err)
}
v.enforceIdKeyDigest = *config.Provider.Azure.EnforceIdKeyDigest
v.idkeydigest = idkeydigest
}
return &v, nil
}
@ -116,15 +129,15 @@ func (v *Validator) PCRS() map[uint32][]byte {
}
func (v *Validator) updateValidator(cmd *cobra.Command) {
log := warnLogger{cmd: cmd}
switch v.provider {
case cloudprovider.GCP:
v.validator = gcp.NewValidator(v.pcrs, v.enforcedPCRs)
v.validator = gcp.NewValidator(v.pcrs, v.enforcedPCRs, log)
case cloudprovider.Azure:
v.validator = azure.NewValidator(v.pcrs, v.enforcedPCRs)
v.validator = azure.NewValidator(v.pcrs, v.enforcedPCRs, v.idkeydigest, v.enforceIdKeyDigest, log)
case cloudprovider.QEMU:
v.validator = qemu.NewValidator(v.pcrs, v.enforcedPCRs)
v.validator = qemu.NewValidator(v.pcrs, v.enforcedPCRs, log)
}
v.validator.AddLogger(warnLogger{cmd: cmd})
}
func (v *Validator) checkPCRs(pcrs map[uint32][]byte, enforcedPCRs []uint32) error {

View File

@ -29,10 +29,12 @@ func TestNewValidator(t *testing.T) {
}
testCases := map[string]struct {
provider cloudprovider.Provider
config *config.Config
pcrs map[uint32][]byte
wantErr bool
provider cloudprovider.Provider
config *config.Config
pcrs map[uint32][]byte
enforceIdKeyDigest bool
idkeydigest string
wantErr bool
}{
"gcp": {
provider: cloudprovider.GCP,
@ -61,6 +63,19 @@ func TestNewValidator(t *testing.T) {
pcrs: testPCRs,
wantErr: true,
},
"set idkeydigest": {
provider: cloudprovider.Azure,
pcrs: testPCRs,
idkeydigest: "414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141",
enforceIdKeyDigest: true,
},
"invalid idkeydigest": {
provider: cloudprovider.Azure,
pcrs: testPCRs,
idkeydigest: "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414",
enforceIdKeyDigest: true,
wantErr: true,
},
}
for name, tc := range testCases {
@ -74,7 +89,7 @@ func TestNewValidator(t *testing.T) {
}
if tc.provider == cloudprovider.Azure {
measurements := config.Measurements(tc.pcrs)
conf.Provider.Azure = &config.AzureConfig{Measurements: measurements}
conf.Provider.Azure = &config.AzureConfig{Measurements: measurements, EnforceIdKeyDigest: &tc.enforceIdKeyDigest, IdKeyDigest: tc.idkeydigest}
}
if tc.provider == cloudprovider.QEMU {
measurements := config.Measurements(tc.pcrs)
@ -96,6 +111,7 @@ func TestNewValidator(t *testing.T) {
func TestValidatorV(t *testing.T) {
zero := []byte("00000000000000000000000000000000")
newTestPCRs := func() map[uint32][]byte {
return map[uint32][]byte{
0: zero,
@ -122,17 +138,17 @@ func TestValidatorV(t *testing.T) {
"gcp": {
provider: cloudprovider.GCP,
pcrs: newTestPCRs(),
wantVs: gcp.NewValidator(newTestPCRs(), nil),
wantVs: gcp.NewValidator(newTestPCRs(), nil, nil),
},
"azure": {
provider: cloudprovider.Azure,
pcrs: newTestPCRs(),
wantVs: azure.NewValidator(newTestPCRs(), nil),
wantVs: azure.NewValidator(newTestPCRs(), nil, nil, false, nil),
},
"qemu": {
provider: cloudprovider.QEMU,
pcrs: newTestPCRs(),
wantVs: qemu.NewValidator(newTestPCRs(), nil),
wantVs: qemu.NewValidator(newTestPCRs(), nil, nil),
},
}

View File

@ -140,6 +140,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
SshUserKeys: ssh.ToProtoSlice(sshUsers),
HelmDeployments: helmDeployments,
EnforcedPcrs: getEnforcedMeasurements(provider, config),
EnforceIdkeydigest: getEnforceIdKeyDigest(provider, config),
}
resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req)
if err != nil {
@ -236,6 +237,15 @@ func getEnforcedMeasurements(provider cloudprovider.Provider, config *config.Con
}
}
func getEnforceIdKeyDigest(provider cloudprovider.Provider, config *config.Config) bool {
switch provider {
case cloudprovider.Azure:
return *config.Provider.Azure.EnforceIdKeyDigest
default:
return false
}
}
// evalFlagArgs gets the flag values and does preprocessing of these values like
// reading the content from file path flags and deriving other values from flag combinations.
func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, error) {

View File

@ -14,7 +14,6 @@ import (
"github.com/edgelesssys/constellation/bootstrapper/initproto"
"github.com/edgelesssys/constellation/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/internal/config"
@ -505,8 +504,6 @@ func (v *testValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
return attestation.UserData, nil
}
func (v *testValidator) AddLogger(vtpm.WarnLogger) {}
type testIssuer struct {
oid.Getter
pcrs map[uint32][]byte

View File

@ -17,7 +17,6 @@ import (
"math/big"
"time"
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/crypto"
"github.com/edgelesssys/constellation/internal/oid"
)
@ -72,7 +71,6 @@ type Issuer interface {
type Validator interface {
oid.Getter
Validate(attDoc []byte, nonce []byte) ([]byte, error)
AddLogger(log vtpm.WarnLogger)
}
// getATLSConfigForClientFunc returns a config setup function that is called once for every client connecting to the server.
@ -367,9 +365,6 @@ func NewFakeValidators(oid oid.Getter) []Validator {
return []Validator{NewFakeValidator(oid)}
}
// AddLogger is a nop for FakeValidator.
func (v FakeValidator) AddLogger(log vtpm.WarnLogger) {}
// Validate unmarshals the attestation document and verifies the nonce.
func (v FakeValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
var doc FakeAttestationDoc

View File

@ -21,7 +21,7 @@ func TestAttestation(t *testing.T) {
require := require.New(t)
issuer := NewIssuer()
validator := NewValidator(map[uint32][]byte{}, nil) // TODO: check for list of expected Azure PCRs
validator := NewValidator(map[uint32][]byte{}, nil, nil, false, nil) // TODO: check for list of expected Azure PCRs
nonce := []byte{2, 3, 4}
challenge := []byte("Constellation")

View File

@ -18,6 +18,7 @@ const (
lenHclHeader = 0x20
lenSnpReport = 0x4a0
lenSnpReportRuntimeDataPadding = 0x14
tpmReportIdx = 0x01400001
)
// Issuer for Azure TPM attestation.
@ -41,6 +42,27 @@ func NewIssuer() *Issuer {
}
}
// GetIdKeyDigest reads the idkeydigest from the snp report saved in the TPM's non-volatile memory.
func GetIdKeyDigest(open vtpm.TPMOpenFunc) ([]byte, error) {
tpm, err := open()
if err != nil {
return nil, err
}
defer tpm.Close()
reportRaw, err := tpm2.NVReadEx(tpm, tpmReportIdx, tpm2.HandleOwner, "", 0)
if err != nil {
return nil, fmt.Errorf("reading idx %x from TMP: %w", tpmReportIdx, err)
}
report, err := newSNPReportFromBytes(reportRaw)
if err != nil {
return nil, fmt.Errorf("creating snp report: %w", err)
}
return report.IdKeyDigest[:], nil
}
func hclAkTemplate() tpm2.Public {
akFlags := tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA | tpm2.FlagRestricted | tpm2.FlagSign
return tpm2.Public{
@ -113,7 +135,7 @@ func getSNPAttestation(reportGetter tpmReportGetter, imdsAPI imdsApi) func(tpm i
type tpmReport struct{}
func (s *tpmReport) get(tpm io.ReadWriteCloser) ([]byte, error) {
return tpm2.NVReadEx(tpm, 0x01400001, tpm2.HandleOwner, "", 0)
return tpm2.NVReadEx(tpm, tpmReportIdx, tpm2.HandleOwner, "", 0)
}
type tpmReportGetter interface {

View File

@ -29,14 +29,15 @@ type Validator struct {
}
// NewValidator initializes a new Azure validator with the provided PCR values.
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32) *Validator {
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32, idkeydigest []byte, enforceIdKeyDigest bool, log vtpm.WarnLogger) *Validator {
return &Validator{
Validator: vtpm.NewValidator(
pcrs,
enforcedPCRs,
trustedKeyFromSNP(&azureInstanceInfo{}),
trustedKeyFromSNP(&azureInstanceInfo{}, idkeydigest, enforceIdKeyDigest, log),
validateAzureCVM,
vtpm.VerifyPKCS1v15,
log,
),
}
}
@ -77,9 +78,21 @@ func (e *vcekError) Error() string {
return fmt.Sprintf("validating VCEK: %v", e.innerError)
}
type idkeyError struct {
expectedValue []byte
}
func (e *idkeyError) Unwrap() error {
return nil
}
func (e *idkeyError) Error() string {
return fmt.Sprintf("configured idkeydigest does not match reported idkeydigest: %x", e.expectedValue)
}
// trustedKeyFromSNP establishes trust in the given public key.
// It does so by verifying the SNP attestation statement in instanceInfo.
func trustedKeyFromSNP(hclAk HCLAkValidator) func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
func trustedKeyFromSNP(hclAk HCLAkValidator, idkeydigest []byte, enforceIdKeyDigest bool, log vtpm.WarnLogger) func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
return func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
var instanceInfo azureInstanceInfo
if err := json.Unmarshal(instanceInfoRaw, &instanceInfo); err != nil {
@ -96,7 +109,7 @@ func trustedKeyFromSNP(hclAk HCLAkValidator) func(akPub, instanceInfoRaw []byte)
return nil, fmt.Errorf("validating VCEK: %w", err)
}
if err = validateSNPReport(vcek, report); err != nil {
if err = validateSNPReport(vcek, idkeydigest, enforceIdKeyDigest, report, log); err != nil {
return nil, fmt.Errorf("validating SNP report: %w", err)
}
@ -163,7 +176,7 @@ func validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) {
return vcek, nil
}
func validateSNPReport(cert *x509.Certificate, report snpAttestationReport) error {
func validateSNPReport(cert *x509.Certificate, expectedIdKeyDigest []byte, enforceIdKeyDigest bool, report snpAttestationReport, log vtpm.WarnLogger) error {
sig_r := report.Signature.R[:]
sig_s := report.Signature.S[:]
@ -189,6 +202,15 @@ func validateSNPReport(cert *x509.Certificate, report snpAttestationReport) erro
return &signatureError{err}
}
if !bytes.Equal(expectedIdKeyDigest, report.IdKeyDigest[:]) {
if enforceIdKeyDigest {
return &idkeyError{report.IdKeyDigest[:]}
}
if log != nil {
log.Warnf("Encountered different than configured idkeydigest value: %x.\n", report.IdKeyDigest[:])
}
}
return nil
}

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,7 @@ func TestAttestation(t *testing.T) {
PCR0 := []byte{0x0f, 0x35, 0xc2, 0x14, 0x60, 0x8d, 0x93, 0xc7, 0xa6, 0xe6, 0x8a, 0xe7, 0x35, 0x9b, 0x4a, 0x8b, 0xe5, 0xa0, 0xe9, 0x9e, 0xea, 0x91, 0x07, 0xec, 0xe4, 0x27, 0xc4, 0xde, 0xa4, 0xe4, 0x39, 0xcf}
issuer := NewIssuer()
validator := NewValidator(map[uint32][]byte{0: PCR0}, nil)
validator := NewValidator(map[uint32][]byte{0: PCR0}, nil, nil)
nonce := []byte{2, 3, 4}
challenge := []byte("Constellation")

View File

@ -28,7 +28,7 @@ type Validator struct {
}
// NewValidator initializes a new GCP validator with the provided PCR values.
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32) *Validator {
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32, log vtpm.WarnLogger) *Validator {
return &Validator{
Validator: vtpm.NewValidator(
pcrs,
@ -36,6 +36,7 @@ func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32) *Validator {
trustedKeyFromGCEAPI(newInstanceClient),
gceNonHostInfoEvent,
vtpm.VerifyPKCS1v15,
log,
),
}
}

View File

@ -15,7 +15,7 @@ type Validator struct {
}
// NewValidator initializes a new qemu validator with the provided PCR values.
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32) *Validator {
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32, log vtpm.WarnLogger) *Validator {
return &Validator{
Validator: vtpm.NewValidator(
pcrs,
@ -23,6 +23,7 @@ func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32) *Validator {
unconditionalTrust,
func(attestation vtpm.AttestationDocument) error { return nil },
vtpm.VerifyPKCS1v15,
log,
),
}
}

View File

@ -138,7 +138,7 @@ type Validator struct {
// NewValidator returns a new Validator.
func NewValidator(expectedPCRs map[uint32][]byte, enforcedPCRs []uint32,
getTrustedKey GetTPMTrustedAttestationPublicKey, validateCVM ValidateCVM, verifyUserData VerifyUserData,
getTrustedKey GetTPMTrustedAttestationPublicKey, validateCVM ValidateCVM, verifyUserData VerifyUserData, log WarnLogger,
) *Validator {
// Convert the enforced PCR list to a map for convenient and fast lookup
enforcedMap := make(map[uint32]struct{})
@ -152,14 +152,10 @@ func NewValidator(expectedPCRs map[uint32][]byte, enforcedPCRs []uint32,
getTrustedKey: getTrustedKey,
validateCVM: validateCVM,
verifyUserData: verifyUserData,
log: log,
}
}
// AddLogger adds a logger to the validator.
func (v *Validator) AddLogger(log WarnLogger) {
v.log = log
}
// Validate a TPM based attestation.
func (v *Validator) Validate(attDocRaw []byte, nonce []byte) ([]byte, error) {
var attDoc AttestationDocument

View File

@ -62,9 +62,10 @@ func TestValidate(t *testing.T) {
0: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
1: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}
warnLog := &testWarnLog{}
issuer := NewIssuer(newSimTPMWithEventLog, tpmclient.AttestationKeyRSA, fakeGetInstanceInfo)
validator := NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15)
validator := NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15, warnLog)
nonce := []byte{1, 2, 3, 4}
challenge := []byte("Constellation")
@ -82,7 +83,6 @@ func TestValidate(t *testing.T) {
require.NoError(err)
require.Equal(challenge, out)
warnLog := &testWarnLog{}
enforcedPCRs := []uint32{0, 1}
expectedPCRs := map[uint32][]byte{
0: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
@ -98,8 +98,8 @@ func TestValidate(t *testing.T) {
fakeGetTrustedKey,
fakeValidateCVM,
VerifyPKCS1v15,
warnLog,
)
warningValidator.AddLogger(warnLog)
out, err = warningValidator.Validate(attDocRaw, nonce)
require.NoError(err)
assert.Equal(t, challenge, out)
@ -112,13 +112,13 @@ func TestValidate(t *testing.T) {
wantErr bool
}{
"invalid nonce": {
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: []byte{4, 3, 2, 1},
wantErr: true,
},
"invalid signature": {
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(AttestationDocument{
Attestation: attDoc.Attestation,
InstanceInfo: attDoc.InstanceInfo,
@ -135,7 +135,7 @@ func TestValidate(t *testing.T) {
func(akPub, instanceInfo []byte) (crypto.PublicKey, error) {
return nil, errors.New("untrusted")
},
fakeValidateCVM, VerifyPKCS1v15),
fakeValidateCVM, VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
@ -148,7 +148,7 @@ func TestValidate(t *testing.T) {
func(attestation AttestationDocument) error {
return errors.New("untrusted")
},
VerifyPKCS1v15),
VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
@ -161,13 +161,13 @@ func TestValidate(t *testing.T) {
[]uint32{0},
fakeGetTrustedKey,
fakeValidateCVM,
VerifyPKCS1v15),
VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
},
"no sha256 quote": {
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15, warnLog),
attDoc: mustMarshalAttestation(AttestationDocument{
Attestation: &attest.Attestation{
AkPub: attDoc.Attestation.AkPub,
@ -185,7 +185,7 @@ func TestValidate(t *testing.T) {
wantErr: true,
},
"invalid attestation document": {
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
validator: NewValidator(testExpectedPCRs, []uint32{0, 1}, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15, warnLog),
attDoc: []byte("invalid attestation"),
nonce: nonce,
wantErr: true,

View File

@ -167,6 +167,12 @@ type AzureConfig struct {
// List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning.
EnforcedMeasurements []uint32 `yaml:"enforcedMeasurements"`
// description: |
// Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf
IdKeyDigest string `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty,hexadecimal,len=96"`
// description: |
// Enforce the specified idKeyDigest value during remote attestation.
EnforceIdKeyDigest *bool `yaml:"enforceIdKeyDigest" validate:"required"`
// description: |
// Use VMs with security type Confidential VM. If set to false, Trusted Launch VMs will be used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview
ConfidentialVM *bool `yaml:"confidentialVM" validate:"required"`
}
@ -247,7 +253,6 @@ func Default() *Config {
},
},
Provider: ProviderConfig{
// TODO remove our subscriptions from the default config
Azure: &AzureConfig{
SubscriptionID: "",
TenantID: "",
@ -258,6 +263,8 @@ func Default() *Config {
StateDiskType: "Premium_LRS",
Measurements: copyPCRMap(azurePCRs),
EnforcedMeasurements: []uint32{8, 9, 11, 12},
IdKeyDigest: "57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696",
EnforceIdKeyDigest: func() *bool { b := true; return &b }(),
ConfidentialVM: func() *bool { b := true; return &b }(),
},
GCP: &GCPConfig{

View File

@ -199,7 +199,7 @@ func init() {
FieldName: "azure",
},
}
AzureConfigDoc.Fields = make([]encoder.Doc, 12)
AzureConfigDoc.Fields = make([]encoder.Doc, 14)
AzureConfigDoc.Fields[0].Name = "subscription"
AzureConfigDoc.Fields[0].Type = "string"
AzureConfigDoc.Fields[0].Note = ""
@ -255,11 +255,21 @@ func init() {
AzureConfigDoc.Fields[10].Note = ""
AzureConfigDoc.Fields[10].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
AzureConfigDoc.Fields[11].Name = "confidentialVM"
AzureConfigDoc.Fields[11].Type = "bool"
AzureConfigDoc.Fields[11].Name = "idKeyDigest"
AzureConfigDoc.Fields[11].Type = "string"
AzureConfigDoc.Fields[11].Note = ""
AzureConfigDoc.Fields[11].Description = "Use VMs with security type Confidential VM. If set to false, Trusted Launch VMs will be used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Use VMs with security type Confidential VM. If set to false, Trusted Launch VMs will be used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
AzureConfigDoc.Fields[11].Description = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
AzureConfigDoc.Fields[12].Name = "enforceIdKeyDigest"
AzureConfigDoc.Fields[12].Type = "bool"
AzureConfigDoc.Fields[12].Note = ""
AzureConfigDoc.Fields[12].Description = "Enforce the specified idKeyDigest value during remote attestation."
AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "Enforce the specified idKeyDigest value during remote attestation."
AzureConfigDoc.Fields[13].Name = "confidentialVM"
AzureConfigDoc.Fields[13].Type = "bool"
AzureConfigDoc.Fields[13].Note = ""
AzureConfigDoc.Fields[13].Description = "Use VMs with security type Confidential VM. If set to false, Trusted Launch VMs will be used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
AzureConfigDoc.Fields[13].Comments[encoder.LineComment] = "Use VMs with security type Confidential VM. If set to false, Trusted Launch VMs will be used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
GCPConfigDoc.Type = "GCPConfig"
GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI."

View File

@ -74,6 +74,10 @@ const (
MeasurementSaltFilename = "measurementSalt"
// MeasurementSecretFilename is the filename of the secret used in creation of the clusterID.
MeasurementSecretFilename = "measurementSecret"
// IdKeyDigestFilename is the name of the file holding the currently enforced idkeydigest.
IdKeyDigestFilename = "idkeydigest"
// EnforceIdKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
EnforceIdKeyDigestFilename = "enforceIdKeyDigest"
// K8sVersion is the filename of the mapped "k8s-version" configMap file.
K8sVersion = "k8s-version"

View File

@ -11,7 +11,6 @@ import (
"github.com/edgelesssys/constellation/bootstrapper/initproto"
"github.com/edgelesssys/constellation/internal/atls"
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
@ -101,8 +100,6 @@ func (v fakeValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
return doc.UserData, v.err
}
func (v fakeValidator) AddLogger(vtpm.WarnLogger) {}
type fakeOID asn1.ObjectIdentifier
func (o fakeOID) OID() asn1.ObjectIdentifier {

View File

@ -30,10 +30,10 @@ func IsSupportedK8sVersion(version string) bool {
const (
// Constellation images.
// These images are built in a way that they support all versions currently listed in VersionConfigs.
JoinImage = "ghcr.io/edgelesssys/constellation/join-service:v0.0.1"
JoinImage = "ghcr.io/edgelesssys/constellation/join-service:v0.0.1-0.20220831112436-10766c6049b8"
AccessManagerImage = "ghcr.io/edgelesssys/constellation/access-manager:v0.0.1"
KmsImage = "ghcr.io/edgelesssys/constellation/kmsserver:v0.0.1"
VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v0.0.1"
VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v0.0.1-0.20220831112436-10766c6049b8"
GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:20220713.00"
NodeOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-operator-catalog"
NodeOperatorVersion = "v0.0.1"

View File

@ -2,8 +2,10 @@ package watcher
import (
"encoding/asn1"
"encoding/hex"
"fmt"
"path/filepath"
"strconv"
"sync"
"github.com/edgelesssys/constellation/internal/atls"
@ -22,6 +24,7 @@ type Updatable struct {
mux sync.Mutex
newValidator newValidatorFunc
fileHandler file.Handler
csp cloudprovider.Provider
atls.Validator
}
@ -30,11 +33,17 @@ func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler) (*Up
var newValidator newValidatorFunc
switch cloudprovider.FromString(csp) {
case cloudprovider.Azure:
newValidator = func(m map[uint32][]byte, e []uint32) atls.Validator { return azure.NewValidator(m, e) }
newValidator = func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator {
return azure.NewValidator(m, e, idkeydigest, enforceIdKeyDigest, log)
}
case cloudprovider.GCP:
newValidator = func(m map[uint32][]byte, e []uint32) atls.Validator { return gcp.NewValidator(m, e) }
newValidator = func(m map[uint32][]byte, e []uint32, _ []byte, _ bool, log *logger.Logger) atls.Validator {
return gcp.NewValidator(m, e, log)
}
case cloudprovider.QEMU:
newValidator = func(m map[uint32][]byte, e []uint32) atls.Validator { return qemu.NewValidator(m, e) }
newValidator = func(m map[uint32][]byte, e []uint32, _ []byte, _ bool, log *logger.Logger) atls.Validator {
return qemu.NewValidator(m, e, log)
}
default:
return nil, fmt.Errorf("unknown cloud service provider: %q", csp)
}
@ -43,6 +52,7 @@ func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler) (*Up
log: log,
newValidator: newValidator,
fileHandler: fileHandler,
csp: cloudprovider.FromString(csp),
}
if err := u.Update(); err != nil {
@ -82,10 +92,35 @@ func (u *Updatable) Update() error {
}
u.log.Debugf("Enforced PCRs: %v", enforced)
u.Validator = u.newValidator(measurements, enforced)
u.Validator.AddLogger(u.log)
var idkeydigest []byte
var enforceIdKeyDigest bool
if u.csp == cloudprovider.Azure {
u.log.Infof("Updating encforceIdKeyDigest value")
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename))
if err != nil {
return err
}
enforceIdKeyDigest, err = strconv.ParseBool(string(enforceRaw))
if err != nil {
return fmt.Errorf("parsing content of EnforceIdKeyDigestFilename: %s: %w", enforceRaw, err)
}
u.log.Debugf("New encforceIdKeyDigest value: %v", enforceIdKeyDigest)
u.log.Infof("Updating expected idkeydigest")
idkeydigestRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.IdKeyDigestFilename))
if err != nil {
return err
}
idkeydigest, err = hex.DecodeString(string(idkeydigestRaw))
if err != nil {
return fmt.Errorf("parsing hexstring: %s: %w", idkeydigestRaw, err)
}
u.log.Debugf("New idkeydigest: %x", idkeydigest)
}
u.Validator = u.newValidator(measurements, enforced, idkeydigest, enforceIdKeyDigest, u.log)
return nil
}
type newValidatorFunc func(measurements map[uint32][]byte, enforcedPCRs []uint32) atls.Validator
type newValidatorFunc func(measurements map[uint32][]byte, enforcedPCRs []uint32, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator

View File

@ -14,7 +14,6 @@ import (
"testing"
"github.com/edgelesssys/constellation/internal/atls"
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/file"
"github.com/edgelesssys/constellation/internal/logger"
@ -78,6 +77,14 @@ func TestNewUpdateableValidator(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IdKeyDigestFilename),
[]byte{},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
[]byte("false"),
))
}
_, err := NewValidator(
@ -99,7 +106,7 @@ func TestUpdate(t *testing.T) {
require := require.New(t)
oid := fakeOID{1, 3, 9900, 1}
newValidator := func(m map[uint32][]byte, e []uint32) atls.Validator {
newValidator := func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
return fakeValidator{fakeOID: oid}
}
handler := file.NewHandler(afero.NewMemMapFs())
@ -126,6 +133,14 @@ func TestUpdate(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IdKeyDigestFilename),
[]byte{},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
[]byte("false"),
))
// call update once to initialize the server's validator
require.NoError(validator.Update())
@ -169,7 +184,7 @@ func TestUpdateConcurrency(t *testing.T) {
validator := &Updatable{
log: logger.NewTest(t),
fileHandler: handler,
newValidator: func(m map[uint32][]byte, e []uint32) atls.Validator {
newValidator: func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}}
},
}
@ -184,6 +199,14 @@ func TestUpdateConcurrency(t *testing.T) {
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
[]uint32{11},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IdKeyDigestFilename),
[]byte{},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
[]byte("false"),
))
var wg sync.WaitGroup
@ -232,8 +255,6 @@ func (v fakeValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
return doc.UserData, v.err
}
func (v fakeValidator) AddLogger(logger vtpm.WarnLogger) {}
type fakeOID asn1.ObjectIdentifier
func (o fakeOID) OID() asn1.ObjectIdentifier {