mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-11 15:39:33 -05:00
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:
parent
c84e44913b
commit
4adc19b7f5
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -21,6 +21,7 @@ message InitRequest {
|
||||
bytes salt = 10;
|
||||
bytes helm_deployments = 11;
|
||||
repeated uint32 enforced_pcrs = 12;
|
||||
bool enforce_idkeydigest = 13;
|
||||
}
|
||||
|
||||
message InitResponse {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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 {
|
||||
|
@ -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
@ -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")
|
||||
|
@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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{
|
||||
|
@ -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."
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user