mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-19 11:44:20 -04:00
AB#2386: TrustedLaunch support for azure attestation
* There are now two attestation packages on azure. The issuer on the server side is created base on successfully querying the idkeydigest from the TPM. Fallback on err: Trusted Launch. * The bootstrapper's issuer choice is validated by the CLI's validator, which is created based on the local config. * Add "azureCVM" field to new "internal-config" cm. This field is populated by the bootstrapper. * Group attestation OIDs by CSP (#42) * Bootstrapper now uses IssuerWrapper type to pass the issuer (and some context info) to the initserver. * Introduce VMType package akin to cloudprovider. Used by IssuerWrapper. * Extend unittests. * Remove CSP specific attestation integration tests Co-authored-by: <dw@edgeless.systems> Signed-off-by: Otto Bittner <cobittner@posteo.net>
This commit is contained in:
parent
4bfb98d35a
commit
405db3286e
33 changed files with 749 additions and 431 deletions
|
@ -14,21 +14,24 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/bootstrapper/internal/initserver"
|
||||||
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes"
|
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes"
|
||||||
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi"
|
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi"
|
||||||
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/kubectl"
|
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/kubectl"
|
||||||
"github.com/edgelesssys/constellation/bootstrapper/internal/logging"
|
"github.com/edgelesssys/constellation/bootstrapper/internal/logging"
|
||||||
"github.com/edgelesssys/constellation/internal/atls"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/azure"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/snp"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/azure/trustedlaunch"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/simulator"
|
"github.com/edgelesssys/constellation/internal/attestation/simulator"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
azurecloud "github.com/edgelesssys/constellation/internal/cloud/azure"
|
azurecloud "github.com/edgelesssys/constellation/internal/cloud/azure"
|
||||||
|
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||||
gcpcloud "github.com/edgelesssys/constellation/internal/cloud/gcp"
|
gcpcloud "github.com/edgelesssys/constellation/internal/cloud/gcp"
|
||||||
qemucloud "github.com/edgelesssys/constellation/internal/cloud/qemu"
|
qemucloud "github.com/edgelesssys/constellation/internal/cloud/qemu"
|
||||||
|
"github.com/edgelesssys/constellation/internal/cloud/vmtype"
|
||||||
"github.com/edgelesssys/constellation/internal/constants"
|
"github.com/edgelesssys/constellation/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/internal/file"
|
"github.com/edgelesssys/constellation/internal/file"
|
||||||
"github.com/edgelesssys/constellation/internal/iproute"
|
"github.com/edgelesssys/constellation/internal/iproute"
|
||||||
|
@ -65,20 +68,20 @@ func main() {
|
||||||
var clusterInitJoiner clusterInitJoiner
|
var clusterInitJoiner clusterInitJoiner
|
||||||
var metadataAPI metadataAPI
|
var metadataAPI metadataAPI
|
||||||
var cloudLogger logging.CloudLogger
|
var cloudLogger logging.CloudLogger
|
||||||
var issuer atls.Issuer
|
var issuer initserver.IssuerWrapper
|
||||||
var openTPM vtpm.TPMOpenFunc
|
var openTPM vtpm.TPMOpenFunc
|
||||||
var fs afero.Fs
|
var fs afero.Fs
|
||||||
|
|
||||||
switch strings.ToLower(os.Getenv(constellationCSP)) {
|
switch cloudprovider.FromString(os.Getenv(constellationCSP)) {
|
||||||
case "aws":
|
case cloudprovider.AWS:
|
||||||
panic("AWS cloud provider currently unsupported")
|
panic("AWS cloud provider currently unsupported")
|
||||||
case "gcp":
|
case cloudprovider.GCP:
|
||||||
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.GCPPCRSelection)
|
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.GCPPCRSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer = gcp.NewIssuer()
|
issuer = initserver.NewIssuerWrapper(gcp.NewIssuer(), vmtype.Unknown, nil)
|
||||||
|
|
||||||
gcpClient, err := gcpcloud.NewClient(ctx)
|
gcpClient, err := gcpcloud.NewClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -100,7 +103,7 @@ func main() {
|
||||||
}
|
}
|
||||||
clusterInitJoiner = kubernetes.New(
|
clusterInitJoiner = kubernetes.New(
|
||||||
"gcp", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &gcpcloud.CloudControllerManager{},
|
"gcp", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &gcpcloud.CloudControllerManager{},
|
||||||
&gcpcloud.CloudNodeManager{}, &gcpcloud.Autoscaler{}, metadata, pcrsJSON, nil,
|
&gcpcloud.CloudNodeManager{}, &gcpcloud.Autoscaler{}, metadata, pcrsJSON,
|
||||||
)
|
)
|
||||||
openTPM = vtpm.OpenVTPM
|
openTPM = vtpm.OpenVTPM
|
||||||
fs = afero.NewOsFs()
|
fs = afero.NewOsFs()
|
||||||
|
@ -108,19 +111,19 @@ func main() {
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to set loadbalancer route")
|
log.With(zap.Error(err)).Fatalf("Failed to set loadbalancer route")
|
||||||
}
|
}
|
||||||
log.Infof("Added load balancer IP to routing table")
|
log.Infof("Added load balancer IP to routing table")
|
||||||
case "azure":
|
case cloudprovider.Azure:
|
||||||
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.AzurePCRSelection)
|
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.AzurePCRSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
||||||
}
|
}
|
||||||
|
|
||||||
idKeyDigest, err := azure.GetIdKeyDigest(vtpm.OpenVTPM)
|
if idkeydigest, err := snp.GetIdKeyDigest(vtpm.OpenVTPM); err == nil {
|
||||||
if err != nil {
|
issuer = initserver.NewIssuerWrapper(snp.NewIssuer(), vmtype.AzureCVM, idkeydigest)
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to get idkeydigest")
|
} else {
|
||||||
|
// assume we are running in a trusted-launch VM
|
||||||
|
issuer = initserver.NewIssuerWrapper(trustedlaunch.NewIssuer(), vmtype.AzureTrustedLaunch, idkeydigest)
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer = azure.NewIssuer()
|
|
||||||
|
|
||||||
metadata, err := azurecloud.NewMetadata(ctx)
|
metadata, err := azurecloud.NewMetadata(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to create Azure metadata client")
|
log.With(zap.Error(err)).Fatalf("Failed to create Azure metadata client")
|
||||||
|
@ -136,18 +139,18 @@ func main() {
|
||||||
}
|
}
|
||||||
clusterInitJoiner = kubernetes.New(
|
clusterInitJoiner = kubernetes.New(
|
||||||
"azure", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata),
|
"azure", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata),
|
||||||
&azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata, pcrsJSON, idKeyDigest,
|
&azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata, pcrsJSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
openTPM = vtpm.OpenVTPM
|
openTPM = vtpm.OpenVTPM
|
||||||
fs = afero.NewOsFs()
|
fs = afero.NewOsFs()
|
||||||
case "qemu":
|
case cloudprovider.QEMU:
|
||||||
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.QEMUPCRSelection)
|
pcrs, err := vtpm.GetSelectedPCRs(vtpm.OpenVTPM, vtpm.QEMUPCRSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs")
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer = qemu.NewIssuer()
|
issuer = initserver.NewIssuerWrapper(qemu.NewIssuer(), vmtype.Unknown, nil)
|
||||||
|
|
||||||
cloudLogger = qemucloud.NewLogger()
|
cloudLogger = qemucloud.NewLogger()
|
||||||
metadata := &qemucloud.Metadata{}
|
metadata := &qemucloud.Metadata{}
|
||||||
|
@ -157,14 +160,14 @@ func main() {
|
||||||
}
|
}
|
||||||
clusterInitJoiner = kubernetes.New(
|
clusterInitJoiner = kubernetes.New(
|
||||||
"qemu", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &qemucloud.CloudControllerManager{},
|
"qemu", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), &qemucloud.CloudControllerManager{},
|
||||||
&qemucloud.CloudNodeManager{}, &qemucloud.Autoscaler{}, metadata, pcrsJSON, nil,
|
&qemucloud.CloudNodeManager{}, &qemucloud.Autoscaler{}, metadata, pcrsJSON,
|
||||||
)
|
)
|
||||||
metadataAPI = metadata
|
metadataAPI = metadata
|
||||||
|
|
||||||
openTPM = vtpm.OpenVTPM
|
openTPM = vtpm.OpenVTPM
|
||||||
fs = afero.NewOsFs()
|
fs = afero.NewOsFs()
|
||||||
default:
|
default:
|
||||||
issuer = atls.NewFakeIssuer(oid.Dummy{})
|
issuer = initserver.NewIssuerWrapper(atls.NewFakeIssuer(oid.Dummy{}), vmtype.Unknown, nil)
|
||||||
clusterInitJoiner = &clusterFake{}
|
clusterInitJoiner = &clusterFake{}
|
||||||
metadataAPI = &providerMetadataFake{}
|
metadataAPI = &providerMetadataFake{}
|
||||||
cloudLogger = &logging.NopLogger{}
|
cloudLogger = &logging.NopLogger{}
|
||||||
|
|
|
@ -20,13 +20,12 @@ import (
|
||||||
"github.com/edgelesssys/constellation/internal/file"
|
"github.com/edgelesssys/constellation/internal/file"
|
||||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||||
"github.com/edgelesssys/constellation/internal/logger"
|
"github.com/edgelesssys/constellation/internal/logger"
|
||||||
"github.com/edgelesssys/constellation/internal/oid"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.0.0"
|
var version = "0.0.0"
|
||||||
|
|
||||||
func run(issuer quoteIssuer, tpm vtpm.TPMOpenFunc, fileHandler file.Handler,
|
func run(issuerWrapper initserver.IssuerWrapper, tpm vtpm.TPMOpenFunc, fileHandler file.Handler,
|
||||||
kube clusterInitJoiner, metadata metadataAPI,
|
kube clusterInitJoiner, metadata metadataAPI,
|
||||||
bindIP, bindPort string, log *logger.Logger,
|
bindIP, bindPort string, log *logger.Logger,
|
||||||
cloudLogger logging.CloudLogger,
|
cloudLogger logging.CloudLogger,
|
||||||
|
@ -58,9 +57,9 @@ func run(issuer quoteIssuer, tpm vtpm.TPMOpenFunc, fileHandler file.Handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeLock := nodelock.New(tpm)
|
nodeLock := nodelock.New(tpm)
|
||||||
initServer := initserver.New(nodeLock, kube, issuer, fileHandler, log)
|
initServer := initserver.New(nodeLock, kube, issuerWrapper, fileHandler, log)
|
||||||
|
|
||||||
dialer := dialer.New(issuer, nil, &net.Dialer{})
|
dialer := dialer.New(issuerWrapper, nil, &net.Dialer{})
|
||||||
joinClient := joinclient.New(nodeLock, dialer, kube, metadata, log)
|
joinClient := joinclient.New(nodeLock, dialer, kube, metadata, log)
|
||||||
|
|
||||||
cleaner := clean.New().With(initServer).With(joinClient)
|
cleaner := clean.New().With(initServer).With(joinClient)
|
||||||
|
@ -92,12 +91,6 @@ type clusterInitJoiner interface {
|
||||||
StartKubelet() error
|
StartKubelet() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type quoteIssuer interface {
|
|
||||||
oid.Getter
|
|
||||||
// Issue issues a quote for remote attestation for a given message
|
|
||||||
Issue(userData []byte, nonce []byte) (quote []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type metadataAPI interface {
|
type metadataAPI interface {
|
||||||
joinclient.MetadataAPI
|
joinclient.MetadataAPI
|
||||||
GetLoadBalancerEndpoint(ctx context.Context) (string, error)
|
GetLoadBalancerEndpoint(ctx context.Context) (string, error)
|
||||||
|
|
|
@ -21,7 +21,7 @@ type clusterFake struct{}
|
||||||
|
|
||||||
// InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster.
|
// InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster.
|
||||||
func (c *clusterFake) InitCluster(
|
func (c *clusterFake) InitCluster(
|
||||||
context.Context, []string, string, string, []byte, []uint32, bool,
|
context.Context, []string, string, string, []byte, []uint32, bool, []byte, bool,
|
||||||
resources.KMSConfig, map[string]string, []byte, *logger.Logger,
|
resources.KMSConfig, map[string]string, []byte, *logger.Logger,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
return []byte{}, nil
|
return []byte{}, nil
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources"
|
"github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources"
|
||||||
"github.com/edgelesssys/constellation/internal/atls"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation"
|
"github.com/edgelesssys/constellation/internal/attestation"
|
||||||
|
"github.com/edgelesssys/constellation/internal/cloud/vmtype"
|
||||||
"github.com/edgelesssys/constellation/internal/crypto"
|
"github.com/edgelesssys/constellation/internal/crypto"
|
||||||
"github.com/edgelesssys/constellation/internal/file"
|
"github.com/edgelesssys/constellation/internal/file"
|
||||||
"github.com/edgelesssys/constellation/internal/grpc/atlscredentials"
|
"github.com/edgelesssys/constellation/internal/grpc/atlscredentials"
|
||||||
|
@ -42,6 +43,7 @@ type Server struct {
|
||||||
fileHandler file.Handler
|
fileHandler file.Handler
|
||||||
grpcServer serveStopper
|
grpcServer serveStopper
|
||||||
cleaner cleaner
|
cleaner cleaner
|
||||||
|
issuerWrapper IssuerWrapper
|
||||||
|
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
|
|
||||||
|
@ -49,18 +51,20 @@ type Server struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new initialization server.
|
// New creates a new initialization server.
|
||||||
func New(lock locker, kube ClusterInitializer, issuer atls.Issuer, fh file.Handler, log *logger.Logger) *Server {
|
func New(lock locker, kube ClusterInitializer, issuerWrapper IssuerWrapper, fh file.Handler, log *logger.Logger) *Server {
|
||||||
log = log.Named("initServer")
|
log = log.Named("initServer")
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
nodeLock: lock,
|
nodeLock: lock,
|
||||||
disk: diskencryption.New(),
|
disk: diskencryption.New(),
|
||||||
initializer: kube,
|
initializer: kube,
|
||||||
fileHandler: fh,
|
fileHandler: fh,
|
||||||
|
issuerWrapper: issuerWrapper,
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(
|
grpcServer := grpc.NewServer(
|
||||||
grpc.Creds(atlscredentials.New(issuer, nil)),
|
grpc.Creds(atlscredentials.New(issuerWrapper, nil)),
|
||||||
grpc.KeepaliveParams(keepalive.ServerParameters{Time: 15 * time.Second}),
|
grpc.KeepaliveParams(keepalive.ServerParameters{Time: 15 * time.Second}),
|
||||||
log.Named("gRPC").GetServerUnaryInterceptor(),
|
log.Named("gRPC").GetServerUnaryInterceptor(),
|
||||||
)
|
)
|
||||||
|
@ -127,6 +131,8 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
|
||||||
measurementSalt,
|
measurementSalt,
|
||||||
req.EnforcedPcrs,
|
req.EnforcedPcrs,
|
||||||
req.EnforceIdkeydigest,
|
req.EnforceIdkeydigest,
|
||||||
|
s.issuerWrapper.IdKeyDigest(),
|
||||||
|
s.issuerWrapper.VMType() == vmtype.AzureCVM,
|
||||||
resources.KMSConfig{
|
resources.KMSConfig{
|
||||||
MasterSecret: req.MasterSecret,
|
MasterSecret: req.MasterSecret,
|
||||||
Salt: req.Salt,
|
Salt: req.Salt,
|
||||||
|
@ -175,6 +181,28 @@ func (s *Server) setupDisk(masterSecret, salt []byte) error {
|
||||||
return s.disk.UpdatePassphrase(string(diskKey))
|
return s.disk.UpdatePassphrase(string(diskKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IssuerWrapper struct {
|
||||||
|
atls.Issuer
|
||||||
|
vmType vmtype.VMType
|
||||||
|
idkeydigest []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIssuerWrapper(issuer atls.Issuer, vmType vmtype.VMType, idkeydigest []byte) IssuerWrapper {
|
||||||
|
return IssuerWrapper{
|
||||||
|
Issuer: issuer,
|
||||||
|
vmType: vmType,
|
||||||
|
idkeydigest: idkeydigest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IssuerWrapper) VMType() vmtype.VMType {
|
||||||
|
return i.vmType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IssuerWrapper) IdKeyDigest() []byte {
|
||||||
|
return i.idkeydigest
|
||||||
|
}
|
||||||
|
|
||||||
func sshProtoKeysToMap(keys []*initproto.SSHUserKey) map[string]string {
|
func sshProtoKeysToMap(keys []*initproto.SSHUserKey) map[string]string {
|
||||||
keyMap := make(map[string]string)
|
keyMap := make(map[string]string)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
@ -211,6 +239,8 @@ type ClusterInitializer interface {
|
||||||
measurementSalt []byte,
|
measurementSalt []byte,
|
||||||
enforcedPcrs []uint32,
|
enforcedPcrs []uint32,
|
||||||
enforceIdKeyDigest bool,
|
enforceIdKeyDigest bool,
|
||||||
|
idKeyDigest []byte,
|
||||||
|
azureCVM bool,
|
||||||
kmsConfig resources.KMSConfig,
|
kmsConfig resources.KMSConfig,
|
||||||
sshUserKeys map[string]string,
|
sshUserKeys map[string]string,
|
||||||
helmDeployments []byte,
|
helmDeployments []byte,
|
||||||
|
|
|
@ -34,7 +34,7 @@ func TestNew(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
fh := file.NewHandler(afero.NewMemMapFs())
|
fh := file.NewHandler(afero.NewMemMapFs())
|
||||||
server := New(newFakeLock(), &stubClusterInitializer{}, nil, fh, logger.NewTest(t))
|
server := New(newFakeLock(), &stubClusterInitializer{}, IssuerWrapper{}, fh, logger.NewTest(t))
|
||||||
assert.NotNil(server)
|
assert.NotNil(server)
|
||||||
assert.NotNil(server.log)
|
assert.NotNil(server.log)
|
||||||
assert.NotNil(server.nodeLock)
|
assert.NotNil(server.nodeLock)
|
||||||
|
@ -289,7 +289,7 @@ type stubClusterInitializer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *stubClusterInitializer) InitCluster(
|
func (i *stubClusterInitializer) InitCluster(
|
||||||
context.Context, []string, string, string, []byte, []uint32, bool,
|
context.Context, []string, string, string, []byte, []uint32, bool, []byte, bool,
|
||||||
resources.KMSConfig, map[string]string, []byte, *logger.Logger,
|
resources.KMSConfig, map[string]string, []byte, *logger.Logger,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
return i.initClusterKubeconfig, i.initClusterErr
|
return i.initClusterKubeconfig, i.initClusterErr
|
||||||
|
|
|
@ -195,6 +195,13 @@ func NewJoinServiceDaemonset(csp, measurementsJSON, enforcedPCRsJSON, initialIdK
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ConfigMap: &k8s.ConfigMapProjection{
|
||||||
|
LocalObjectReference: k8s.LocalObjectReference{
|
||||||
|
Name: constants.InternalConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,13 +53,12 @@ type KubeWrapper struct {
|
||||||
clusterAutoscaler ClusterAutoscaler
|
clusterAutoscaler ClusterAutoscaler
|
||||||
providerMetadata ProviderMetadata
|
providerMetadata ProviderMetadata
|
||||||
initialMeasurementsJSON []byte
|
initialMeasurementsJSON []byte
|
||||||
initialIdKeyDigest []byte
|
|
||||||
getIPAddr func() (string, error)
|
getIPAddr func() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new KubeWrapper with real values.
|
// New creates a new KubeWrapper with real values.
|
||||||
func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client, cloudControllerManager CloudControllerManager,
|
func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client, cloudControllerManager CloudControllerManager,
|
||||||
cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, providerMetadata ProviderMetadata, initialMeasurementsJSON, initialIdKeyDigest []byte,
|
cloudNodeManager CloudNodeManager, clusterAutoscaler ClusterAutoscaler, providerMetadata ProviderMetadata, initialMeasurementsJSON []byte,
|
||||||
) *KubeWrapper {
|
) *KubeWrapper {
|
||||||
return &KubeWrapper{
|
return &KubeWrapper{
|
||||||
cloudProvider: cloudProvider,
|
cloudProvider: cloudProvider,
|
||||||
|
@ -72,7 +71,6 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
|
||||||
clusterAutoscaler: clusterAutoscaler,
|
clusterAutoscaler: clusterAutoscaler,
|
||||||
providerMetadata: providerMetadata,
|
providerMetadata: providerMetadata,
|
||||||
initialMeasurementsJSON: initialMeasurementsJSON,
|
initialMeasurementsJSON: initialMeasurementsJSON,
|
||||||
initialIdKeyDigest: initialIdKeyDigest,
|
|
||||||
getIPAddr: getIPAddr,
|
getIPAddr: getIPAddr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +78,7 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
|
||||||
// InitCluster initializes a new Kubernetes cluster and applies pod network provider.
|
// InitCluster initializes a new Kubernetes cluster and applies pod network provider.
|
||||||
func (k *KubeWrapper) InitCluster(
|
func (k *KubeWrapper) InitCluster(
|
||||||
ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, versionString string, measurementSalt []byte,
|
ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, versionString string, measurementSalt []byte,
|
||||||
enforcedPCRs []uint32, enforceIdKeyDigest bool, kmsConfig resources.KMSConfig, sshUsers map[string]string, helmDeployments []byte, log *logger.Logger,
|
enforcedPCRs []uint32, enforceIdKeyDigest bool, idKeyDigest []byte, azureCVM bool, kmsConfig resources.KMSConfig, sshUsers map[string]string, helmDeployments []byte, log *logger.Logger,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
k8sVersion, err := versions.NewValidK8sVersion(versionString)
|
k8sVersion, err := versions.NewValidK8sVersion(versionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -185,7 +183,11 @@ func (k *KubeWrapper) InitCluster(
|
||||||
return nil, fmt.Errorf("setting up kms: %w", err)
|
return nil, fmt.Errorf("setting up kms: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.setupJoinService(k.cloudProvider, k.initialMeasurementsJSON, measurementSalt, enforcedPCRs, k.initialIdKeyDigest, enforceIdKeyDigest); err != nil {
|
if err := k.setupInternalConfigMap(ctx, strconv.FormatBool(azureCVM)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to setup internal ConfigMap: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := k.setupJoinService(k.cloudProvider, k.initialMeasurementsJSON, measurementSalt, enforcedPCRs, idKeyDigest, enforceIdKeyDigest); err != nil {
|
||||||
return nil, fmt.Errorf("setting up join service failed: %w", err)
|
return nil, fmt.Errorf("setting up join service failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +226,7 @@ func (k *KubeWrapper) InitCluster(
|
||||||
// Store the received k8sVersion in a ConfigMap, overwriting existing values (there shouldn't be any).
|
// Store the received k8sVersion in a ConfigMap, overwriting existing values (there shouldn't be any).
|
||||||
// Joining nodes determine the kubernetes version they will install based on this ConfigMap.
|
// Joining nodes determine the kubernetes version they will install based on this ConfigMap.
|
||||||
if err := k.setupK8sVersionConfigMap(ctx, k8sVersion); err != nil {
|
if err := k.setupK8sVersionConfigMap(ctx, k8sVersion); err != nil {
|
||||||
return nil, fmt.Errorf("failed to setup k8s version ConfigMap: %v", err)
|
return nil, fmt.Errorf("failed to setup k8s version ConfigMap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
k.clusterUtil.FixCilium(nodeName, log)
|
k.clusterUtil.FixCilium(nodeName, log)
|
||||||
|
@ -414,7 +416,32 @@ func (k *KubeWrapper) setupK8sVersionConfigMap(ctx context.Context, k8sVersion v
|
||||||
// We do not use the client's Apply method here since we are handling a kubernetes-native type.
|
// We do not use the client's Apply method here since we are handling a kubernetes-native type.
|
||||||
// These types don't implement our custom Marshaler interface.
|
// These types don't implement our custom Marshaler interface.
|
||||||
if err := k.client.CreateConfigMap(ctx, config); err != nil {
|
if err := k.client.CreateConfigMap(ctx, config); err != nil {
|
||||||
return fmt.Errorf("apply in KubeWrapper.setupK8sVersionConfigMap(..) failed with: %v", err)
|
return fmt.Errorf("apply in KubeWrapper.setupK8sVersionConfigMap(..) failed with: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupInternalConfigMap applies a ConfigMap (cf. server-side apply) to store information that is not supposed to be user-editable.
|
||||||
|
func (k *KubeWrapper) setupInternalConfigMap(ctx context.Context, azureCVM string) error {
|
||||||
|
config := corev1.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: constants.InternalConfigMap,
|
||||||
|
Namespace: "kube-system",
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
constants.AzureCVM: azureCVM,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not use the client's Apply method here since we are handling a kubernetes-native type.
|
||||||
|
// These types don't implement our custom Marshaler interface.
|
||||||
|
if err := k.client.CreateConfigMap(ctx, config); err != nil {
|
||||||
|
return fmt.Errorf("apply in KubeWrapper.setupInternalConfigMap failed with: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -308,7 +308,7 @@ func TestInitCluster(t *testing.T) {
|
||||||
|
|
||||||
_, err := kube.InitCluster(
|
_, err := kube.InitCluster(
|
||||||
context.Background(), autoscalingNodeGroups, serviceAccountURI, string(tc.k8sVersion),
|
context.Background(), autoscalingNodeGroups, serviceAccountURI, string(tc.k8sVersion),
|
||||||
nil, nil, false, resources.KMSConfig{MasterSecret: masterSecret}, nil, nil, logger.NewTest(t),
|
nil, nil, false, nil, true, resources.KMSConfig{MasterSecret: masterSecret}, nil, nil, logger.NewTest(t),
|
||||||
)
|
)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
|
|
|
@ -14,7 +14,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/atls"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/azure"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/snp"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/azure/trustedlaunch"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
@ -29,6 +30,7 @@ type Validator struct {
|
||||||
enforcedPCRs []uint32
|
enforcedPCRs []uint32
|
||||||
idkeydigest []byte
|
idkeydigest []byte
|
||||||
enforceIdKeyDigest bool
|
enforceIdKeyDigest bool
|
||||||
|
azureCVM bool
|
||||||
validator atls.Validator
|
validator atls.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +45,8 @@ func NewValidator(provider cloudprovider.Provider, config *config.Config) (*Vali
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.provider == cloudprovider.Azure {
|
if v.provider == cloudprovider.Azure {
|
||||||
|
v.azureCVM = *config.Provider.Azure.ConfidentialVM
|
||||||
|
if v.azureCVM {
|
||||||
idkeydigest, err := hex.DecodeString(config.Provider.Azure.IdKeyDigest)
|
idkeydigest, err := hex.DecodeString(config.Provider.Azure.IdKeyDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("bad config: decoding idkeydigest from config: %w", err)
|
return nil, fmt.Errorf("bad config: decoding idkeydigest from config: %w", err)
|
||||||
|
@ -50,6 +54,7 @@ func NewValidator(provider cloudprovider.Provider, config *config.Config) (*Vali
|
||||||
v.enforceIdKeyDigest = *config.Provider.Azure.EnforceIdKeyDigest
|
v.enforceIdKeyDigest = *config.Provider.Azure.EnforceIdKeyDigest
|
||||||
v.idkeydigest = idkeydigest
|
v.idkeydigest = idkeydigest
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &v, nil
|
return &v, nil
|
||||||
}
|
}
|
||||||
|
@ -140,7 +145,11 @@ func (v *Validator) updateValidator(cmd *cobra.Command) {
|
||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
v.validator = gcp.NewValidator(v.pcrs, v.enforcedPCRs, log)
|
v.validator = gcp.NewValidator(v.pcrs, v.enforcedPCRs, log)
|
||||||
case cloudprovider.Azure:
|
case cloudprovider.Azure:
|
||||||
v.validator = azure.NewValidator(v.pcrs, v.enforcedPCRs, v.idkeydigest, v.enforceIdKeyDigest, log)
|
if v.azureCVM {
|
||||||
|
v.validator = snp.NewValidator(v.pcrs, v.enforcedPCRs, v.idkeydigest, v.enforceIdKeyDigest, log)
|
||||||
|
} else {
|
||||||
|
v.validator = trustedlaunch.NewValidator(v.pcrs, v.enforcedPCRs, log)
|
||||||
|
}
|
||||||
case cloudprovider.QEMU:
|
case cloudprovider.QEMU:
|
||||||
v.validator = qemu.NewValidator(v.pcrs, v.enforcedPCRs, log)
|
v.validator = qemu.NewValidator(v.pcrs, v.enforcedPCRs, log)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/atls"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/azure"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/snp"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/azure/trustedlaunch"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
@ -40,15 +41,22 @@ func TestNewValidator(t *testing.T) {
|
||||||
pcrs map[uint32][]byte
|
pcrs map[uint32][]byte
|
||||||
enforceIdKeyDigest bool
|
enforceIdKeyDigest bool
|
||||||
idkeydigest string
|
idkeydigest string
|
||||||
|
azureCVM bool
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"gcp": {
|
"gcp": {
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
pcrs: testPCRs,
|
pcrs: testPCRs,
|
||||||
},
|
},
|
||||||
"azure": {
|
"azure cvm": {
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
pcrs: testPCRs,
|
pcrs: testPCRs,
|
||||||
|
azureCVM: true,
|
||||||
|
},
|
||||||
|
"azure trusted launch": {
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
pcrs: testPCRs,
|
||||||
|
azureCVM: false,
|
||||||
},
|
},
|
||||||
"qemu": {
|
"qemu": {
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
|
@ -80,6 +88,7 @@ func TestNewValidator(t *testing.T) {
|
||||||
pcrs: testPCRs,
|
pcrs: testPCRs,
|
||||||
idkeydigest: "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414",
|
idkeydigest: "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414",
|
||||||
enforceIdKeyDigest: true,
|
enforceIdKeyDigest: true,
|
||||||
|
azureCVM: true,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -95,7 +104,7 @@ func TestNewValidator(t *testing.T) {
|
||||||
}
|
}
|
||||||
if tc.provider == cloudprovider.Azure {
|
if tc.provider == cloudprovider.Azure {
|
||||||
measurements := config.Measurements(tc.pcrs)
|
measurements := config.Measurements(tc.pcrs)
|
||||||
conf.Provider.Azure = &config.AzureConfig{Measurements: measurements, EnforceIdKeyDigest: &tc.enforceIdKeyDigest, IdKeyDigest: tc.idkeydigest}
|
conf.Provider.Azure = &config.AzureConfig{Measurements: measurements, EnforceIdKeyDigest: &tc.enforceIdKeyDigest, IdKeyDigest: tc.idkeydigest, ConfidentialVM: &tc.azureCVM}
|
||||||
}
|
}
|
||||||
if tc.provider == cloudprovider.QEMU {
|
if tc.provider == cloudprovider.QEMU {
|
||||||
measurements := config.Measurements(tc.pcrs)
|
measurements := config.Measurements(tc.pcrs)
|
||||||
|
@ -140,16 +149,23 @@ func TestValidatorV(t *testing.T) {
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
pcrs map[uint32][]byte
|
pcrs map[uint32][]byte
|
||||||
wantVs atls.Validator
|
wantVs atls.Validator
|
||||||
|
azureCVM bool
|
||||||
}{
|
}{
|
||||||
"gcp": {
|
"gcp": {
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
pcrs: newTestPCRs(),
|
pcrs: newTestPCRs(),
|
||||||
wantVs: gcp.NewValidator(newTestPCRs(), nil, nil),
|
wantVs: gcp.NewValidator(newTestPCRs(), nil, nil),
|
||||||
},
|
},
|
||||||
"azure": {
|
"azure cvm": {
|
||||||
provider: cloudprovider.Azure,
|
provider: cloudprovider.Azure,
|
||||||
pcrs: newTestPCRs(),
|
pcrs: newTestPCRs(),
|
||||||
wantVs: azure.NewValidator(newTestPCRs(), nil, nil, false, nil),
|
wantVs: snp.NewValidator(newTestPCRs(), nil, nil, false, nil),
|
||||||
|
azureCVM: true,
|
||||||
|
},
|
||||||
|
"azure trusted launch": {
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
pcrs: newTestPCRs(),
|
||||||
|
wantVs: trustedlaunch.NewValidator(newTestPCRs(), nil, nil),
|
||||||
},
|
},
|
||||||
"qemu": {
|
"qemu": {
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
|
@ -162,7 +178,7 @@ func TestValidatorV(t *testing.T) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
validators := &Validator{provider: tc.provider, pcrs: tc.pcrs}
|
validators := &Validator{provider: tc.provider, pcrs: tc.pcrs, azureCVM: tc.azureCVM}
|
||||||
|
|
||||||
resultValidator := validators.V(&cobra.Command{})
|
resultValidator := validators.V(&cobra.Command{})
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
||||||
|
|
||||||
if config.IsAzureNonCVM() {
|
if config.IsAzureNonCVM() {
|
||||||
cmd.Println("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
|
cmd.Println("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
|
||||||
|
if config.EnforcesIdKeyDigest() {
|
||||||
|
cmd.Println("Your config asks for enforcing the idkeydigest. This is only available on Confidential VMs. It will not be enforced.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var instanceType string
|
var instanceType string
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
//go:build azure
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright (c) Edgeless Systems GmbH
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package azure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/goleak"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
goleak.VerifyTestMain(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttestation(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
issuer := NewIssuer()
|
|
||||||
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")
|
|
||||||
|
|
||||||
attDocRaw, err := issuer.Issue(challenge, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
var attDoc vtpm.AttestationDocument
|
|
||||||
err = json.Unmarshal(attDocRaw, &attDoc)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.Equal(challenge, attDoc.UserData)
|
|
||||||
originalPCR := attDoc.Attestation.Quotes[1].Pcrs.Pcrs[uint32(vtpm.PCRIndexOwnerID)]
|
|
||||||
|
|
||||||
out, err := validator.Validate(attDocRaw, nonce)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.Equal(challenge, out)
|
|
||||||
|
|
||||||
// Mark node as intialized. We should still be abe to validate
|
|
||||||
assert.NoError(vtpm.MarkNodeAsBootstrapped(vtpm.OpenVTPM, []byte("Test")))
|
|
||||||
|
|
||||||
attDocRaw, err = issuer.Issue(challenge, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// Make sure the PCR changed
|
|
||||||
err = json.Unmarshal(attDocRaw, &attDoc)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.NotEqual(originalPCR, attDoc.Attestation.Quotes[1].Pcrs.Pcrs[uint32(vtpm.PCRIndexOwnerID)])
|
|
||||||
|
|
||||||
out, err = validator.Validate(attDocRaw, nonce)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.Equal(challenge, out)
|
|
||||||
}
|
|
|
@ -7,147 +7,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
package azure
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"context"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/snp"
|
||||||
"encoding/json"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/trustedlaunch"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
"github.com/edgelesssys/constellation/internal/oid"
|
|
||||||
tpmclient "github.com/google/go-tpm-tools/client"
|
|
||||||
"github.com/google/go-tpm/tpm2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// NewIssuer returns an SNP issuer if it can successfully read the idkeydigest from the TPM.
|
||||||
lenHclHeader = 0x20
|
// Otherwise returns a Trusted Launch issuer.
|
||||||
lenSnpReport = 0x4a0
|
func NewIssuer() atls.Issuer {
|
||||||
lenSnpReportRuntimeDataPadding = 0x14
|
if _, err := snp.GetIdKeyDigest(vtpm.OpenVTPM); err == nil {
|
||||||
tpmReportIdx = 0x01400001
|
return snp.NewIssuer()
|
||||||
)
|
} else {
|
||||||
|
return trustedlaunch.NewIssuer()
|
||||||
// Issuer for Azure TPM attestation.
|
|
||||||
type Issuer struct {
|
|
||||||
oid.Azure
|
|
||||||
*vtpm.Issuer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIssuer initializes a new Azure Issuer.
|
|
||||||
func NewIssuer() *Issuer {
|
|
||||||
imdsAPI := imdsClient{
|
|
||||||
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Issuer{
|
|
||||||
Issuer: vtpm.NewIssuer(
|
|
||||||
vtpm.OpenVTPM,
|
|
||||||
getHCLAttestationKey,
|
|
||||||
getSNPAttestation(&tpmReport{}, imdsAPI),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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[lenHclHeader:])
|
|
||||||
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{
|
|
||||||
Type: tpm2.AlgRSA,
|
|
||||||
NameAlg: tpm2.AlgSHA256,
|
|
||||||
Attributes: akFlags,
|
|
||||||
RSAParameters: &tpm2.RSAParams{
|
|
||||||
Sign: &tpm2.SigScheme{
|
|
||||||
Alg: tpm2.AlgRSASSA,
|
|
||||||
Hash: tpm2.AlgSHA256,
|
|
||||||
},
|
|
||||||
KeyBits: 2048,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHCLAttestationKey reads the attesation key put into the TPM during early boot.
|
|
||||||
func getHCLAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
|
|
||||||
// A minor drawback of `NewCachedKey` is that it will transparently create/overwrite a key if it does not find one matching the template at the given index.
|
|
||||||
// We actually wouldn't want to continue at this point if we realize that the key at the index is not present, due to
|
|
||||||
// easier debuggability. If `NewCachedKey` creates a new key, attestation will fail at the validator.
|
|
||||||
// The function in tpmclient that doesn't create a new key, ReadPublic, can't be used as we would have to create
|
|
||||||
// a tpmclient.Key object manually, which we can't since there is no constructor exported.
|
|
||||||
ak, err := tpmclient.NewCachedKey(tpm, tpm2.HandleOwner, hclAkTemplate(), 0x81000003)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ak, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSNPAttestation loads and returns the SEV-SNP attestation report [1] and the
|
|
||||||
// AMD VCEK certificate chain.
|
|
||||||
// The attestation report is loaded from the TPM, the certificate chain is queried
|
|
||||||
// from the cloud metadata API.
|
|
||||||
// [1] https://github.com/AMDESE/sev-guest/blob/main/include/attestation.h
|
|
||||||
func getSNPAttestation(reportGetter tpmReportGetter, imdsAPI imdsApi) func(tpm io.ReadWriteCloser) ([]byte, error) {
|
|
||||||
return func(tpm io.ReadWriteCloser) ([]byte, error) {
|
|
||||||
hclReport, err := reportGetter.get(tpm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading report from TPM: %w", err)
|
|
||||||
}
|
|
||||||
if len(hclReport) < lenHclHeader+lenSnpReport+lenSnpReportRuntimeDataPadding {
|
|
||||||
return nil, fmt.Errorf("report read from TPM is shorter then expected: %x", hclReport)
|
|
||||||
}
|
|
||||||
hclReport = hclReport[lenHclHeader:]
|
|
||||||
|
|
||||||
runtimeData, _, _ := bytes.Cut(hclReport[lenSnpReport+lenSnpReportRuntimeDataPadding:], []byte{0})
|
|
||||||
|
|
||||||
vcekResponse, err := imdsAPI.getVcek(context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getVcekFromIMDS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceInfo := azureInstanceInfo{
|
|
||||||
Vcek: []byte(vcekResponse.VcekCert),
|
|
||||||
CertChain: []byte(vcekResponse.CertificateChain),
|
|
||||||
AttestationReport: hclReport[:0x4a0],
|
|
||||||
RuntimeData: runtimeData,
|
|
||||||
}
|
|
||||||
statement, err := json.Marshal(instanceInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("marshalling AzureInstanceInfo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return statement, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type tpmReport struct{}
|
|
||||||
|
|
||||||
func (s *tpmReport) get(tpm io.ReadWriteCloser) ([]byte, error) {
|
|
||||||
return tpm2.NVReadEx(tpm, tpmReportIdx, tpm2.HandleOwner, "", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
type tpmReportGetter interface {
|
|
||||||
get(tpm io.ReadWriteCloser) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type imdsApi interface {
|
|
||||||
getVcek(ctx context.Context) (vcekResponse, error)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package azure
|
package snp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
153
internal/attestation/azure/snp/issuer.go
Normal file
153
internal/attestation/azure/snp/issuer.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package snp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/internal/oid"
|
||||||
|
tpmclient "github.com/google/go-tpm-tools/client"
|
||||||
|
"github.com/google/go-tpm/tpm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lenHclHeader = 0x20
|
||||||
|
lenSnpReport = 0x4a0
|
||||||
|
lenSnpReportRuntimeDataPadding = 0x14
|
||||||
|
tpmReportIdx = 0x01400001
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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[lenHclHeader:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating snp report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return report.IdKeyDigest[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issuer for Azure TPM attestation.
|
||||||
|
type Issuer struct {
|
||||||
|
oid.AzureSNP
|
||||||
|
*vtpm.Issuer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIssuer initializes a new Azure Issuer.
|
||||||
|
func NewIssuer() *Issuer {
|
||||||
|
imdsAPI := imdsClient{
|
||||||
|
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Issuer{
|
||||||
|
Issuer: vtpm.NewIssuer(
|
||||||
|
vtpm.OpenVTPM,
|
||||||
|
getAttestationKey,
|
||||||
|
getInstanceInfo(&tpmReport{}, imdsAPI),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInstanceInfo loads and returns the SEV-SNP attestation report [1] and the
|
||||||
|
// AMD VCEK certificate chain.
|
||||||
|
// The attestation report is loaded from the TPM, the certificate chain is queried
|
||||||
|
// from the cloud metadata API.
|
||||||
|
// [1] https://github.com/AMDESE/sev-guest/blob/main/include/attestation.h
|
||||||
|
func getInstanceInfo(reportGetter tpmReportGetter, imdsAPI imdsApi) func(tpm io.ReadWriteCloser) ([]byte, error) {
|
||||||
|
return func(tpm io.ReadWriteCloser) ([]byte, error) {
|
||||||
|
hclReport, err := reportGetter.get(tpm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading report from TPM: %w", err)
|
||||||
|
}
|
||||||
|
if len(hclReport) < lenHclHeader+lenSnpReport+lenSnpReportRuntimeDataPadding {
|
||||||
|
return nil, fmt.Errorf("report read from TPM is shorter then expected: %x", hclReport)
|
||||||
|
}
|
||||||
|
hclReport = hclReport[lenHclHeader:]
|
||||||
|
|
||||||
|
runtimeData, _, _ := bytes.Cut(hclReport[lenSnpReport+lenSnpReportRuntimeDataPadding:], []byte{0})
|
||||||
|
|
||||||
|
vcekResponse, err := imdsAPI.getVcek(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getVcekFromIMDS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceInfo := azureInstanceInfo{
|
||||||
|
Vcek: []byte(vcekResponse.VcekCert),
|
||||||
|
CertChain: []byte(vcekResponse.CertificateChain),
|
||||||
|
AttestationReport: hclReport[:0x4a0],
|
||||||
|
RuntimeData: runtimeData,
|
||||||
|
}
|
||||||
|
statement, err := json.Marshal(instanceInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshalling AzureInstanceInfo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return statement, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hclAkTemplate() tpm2.Public {
|
||||||
|
akFlags := tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA | tpm2.FlagRestricted | tpm2.FlagSign
|
||||||
|
return tpm2.Public{
|
||||||
|
Type: tpm2.AlgRSA,
|
||||||
|
NameAlg: tpm2.AlgSHA256,
|
||||||
|
Attributes: akFlags,
|
||||||
|
RSAParameters: &tpm2.RSAParams{
|
||||||
|
Sign: &tpm2.SigScheme{
|
||||||
|
Alg: tpm2.AlgRSASSA,
|
||||||
|
Hash: tpm2.AlgSHA256,
|
||||||
|
},
|
||||||
|
KeyBits: 2048,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAttestationKey reads the attesation key put into the TPM during early boot.
|
||||||
|
func getAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
|
||||||
|
// A minor drawback of `NewCachedKey` is that it will transparently create/overwrite a key if it does not find one matching the template at the given index.
|
||||||
|
// We actually wouldn't want to continue at this point if we realize that the key at the index is not present, due to
|
||||||
|
// easier debuggability. If `NewCachedKey` creates a new key, attestation will fail at the validator.
|
||||||
|
// The function in tpmclient that doesn't create a new key, ReadPublic, can't be used as we would have to create
|
||||||
|
// a tpmclient.Key object manually, which we can't since there is no constructor exported.
|
||||||
|
ak, err := tpmclient.NewCachedKey(tpm, tpm2.HandleOwner, hclAkTemplate(), 0x81000003)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ak, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tpmReport struct{}
|
||||||
|
|
||||||
|
func (s *tpmReport) get(tpm io.ReadWriteCloser) ([]byte, error) {
|
||||||
|
return tpm2.NVReadEx(tpm, tpmReportIdx, tpm2.HandleOwner, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tpmReportGetter interface {
|
||||||
|
get(tpm io.ReadWriteCloser) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type imdsApi interface {
|
||||||
|
getVcek(ctx context.Context) (vcekResponse, error)
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package azure
|
package snp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -64,7 +64,8 @@ func TestGetSNPAttestation(t *testing.T) {
|
||||||
tpmContent: reportDecoded,
|
tpmContent: reportDecoded,
|
||||||
err: nil,
|
err: nil,
|
||||||
}
|
}
|
||||||
attestationJson, err := getSNPAttestation(&snpAttestationReport, imdsClient)(tpm)
|
|
||||||
|
attestationJson, err := getInstanceInfo(&snpAttestationReport, imdsClient)(tpm)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -96,7 +97,7 @@ func TestGetHCLAttestationKey(t *testing.T) {
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
defer tpm.Close()
|
defer tpm.Close()
|
||||||
|
|
||||||
_, err = getHCLAttestationKey(tpm)
|
_, err = getAttestationKey(tpm)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package azure
|
package snp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -30,7 +30,7 @@ const arkPEM = "-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb
|
||||||
|
|
||||||
// Validator for Azure confidential VM attestation.
|
// Validator for Azure confidential VM attestation.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
oid.Azure
|
oid.AzureSNP
|
||||||
*vtpm.Validator
|
*vtpm.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,65 +40,37 @@ func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32, idkeydigest []b
|
||||||
Validator: vtpm.NewValidator(
|
Validator: vtpm.NewValidator(
|
||||||
pcrs,
|
pcrs,
|
||||||
enforcedPCRs,
|
enforcedPCRs,
|
||||||
trustedKeyFromSNP(&azureInstanceInfo{}, idkeydigest, enforceIdKeyDigest, log),
|
getTrustedKey(&azureInstanceInfo{}, idkeydigest, enforceIdKeyDigest, log),
|
||||||
validateAzureCVM,
|
validateCVM,
|
||||||
vtpm.VerifyPKCS1v15,
|
vtpm.VerifyPKCS1v15,
|
||||||
log,
|
log,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type signatureError struct {
|
// validateCVM is a stub, since SEV-SNP attestation is already verified in trustedKeyFromSNP().
|
||||||
innerError error
|
func validateCVM(attestation vtpm.AttestationDocument) error {
|
||||||
}
|
|
||||||
|
|
||||||
func (e *signatureError) Unwrap() error {
|
|
||||||
return e.innerError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *signatureError) Error() string {
|
|
||||||
return fmt.Sprintf("signature validation failed: %v", e.innerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
type askError struct {
|
|
||||||
innerError error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *askError) Unwrap() error {
|
|
||||||
return e.innerError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *askError) Error() string {
|
|
||||||
return fmt.Sprintf("validating ASK: %v", e.innerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
type vcekError struct {
|
|
||||||
innerError error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *vcekError) Unwrap() error {
|
|
||||||
return e.innerError
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *idkeyError) Error() string {
|
func newSNPReportFromBytes(reportRaw []byte) (snpAttestationReport, error) {
|
||||||
return fmt.Sprintf("configured idkeydigest does not match reported idkeydigest: %x", e.expectedValue)
|
var report snpAttestationReport
|
||||||
|
if err := binary.Read(bytes.NewReader(reportRaw), binary.LittleEndian, &report); err != nil {
|
||||||
|
return snpAttestationReport{}, fmt.Errorf("reading attestation report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// trustedKeyFromSNP establishes trust in the given public key.
|
func reverseEndian(b []byte) {
|
||||||
|
for i := 0; i < len(b)/2; i++ {
|
||||||
|
b[i], b[len(b)-i-1] = b[len(b)-i-1], b[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTrustedKey establishes trust in the given public key.
|
||||||
// It does so by verifying the SNP attestation statement in instanceInfo.
|
// It does so by verifying the SNP attestation statement in instanceInfo.
|
||||||
func trustedKeyFromSNP(hclAk HCLAkValidator, idkeydigest []byte, enforceIdKeyDigest bool, log vtpm.WarnLogger) func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
|
func getTrustedKey(hclAk HCLAkValidator, idkeydigest []byte, enforceIdKeyDigest bool, log vtpm.WarnLogger) func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
|
||||||
return func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
|
return func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) {
|
||||||
var instanceInfo azureInstanceInfo
|
var instanceInfo azureInstanceInfo
|
||||||
if err := json.Unmarshal(instanceInfoRaw, &instanceInfo); err != nil {
|
if err := json.Unmarshal(instanceInfoRaw, &instanceInfo); err != nil {
|
||||||
|
@ -132,26 +104,6 @@ func trustedKeyFromSNP(hclAk HCLAkValidator, idkeydigest []byte, enforceIdKeyDig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reverseEndian(b []byte) {
|
|
||||||
for i := 0; i < len(b)/2; i++ {
|
|
||||||
b[i], b[len(b)-i-1] = b[len(b)-i-1], b[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateAzureCVM is a stub, since SEV-SNP attestation is already verified in trustedKeyFromSNP().
|
|
||||||
func validateAzureCVM(attestation vtpm.AttestationDocument) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSNPReportFromBytes(reportRaw []byte) (snpAttestationReport, error) {
|
|
||||||
var report snpAttestationReport
|
|
||||||
if err := binary.Read(bytes.NewReader(reportRaw), binary.LittleEndian, &report); err != nil {
|
|
||||||
return snpAttestationReport{}, fmt.Errorf("reading attestation report: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return report, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateVCEK takes the PEM-encoded X509 certificate VCEK, ASK and ARK and verifies the integrity of the chain.
|
// validateVCEK takes the PEM-encoded X509 certificate VCEK, ASK and ARK and verifies the integrity of the chain.
|
||||||
// ARK (hardcoded) validates ASK (cloud metadata API) validates VCEK (cloud metadata API).
|
// ARK (hardcoded) validates ASK (cloud metadata API) validates VCEK (cloud metadata API).
|
||||||
func validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) {
|
func validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) {
|
||||||
|
@ -191,9 +143,9 @@ func validateSNPReport(cert *x509.Certificate, expectedIdKeyDigest []byte, enfor
|
||||||
reverseEndian(sig_r)
|
reverseEndian(sig_r)
|
||||||
reverseEndian(sig_s)
|
reverseEndian(sig_s)
|
||||||
|
|
||||||
r := new(big.Int).SetBytes(sig_r)
|
rParam := new(big.Int).SetBytes(sig_r)
|
||||||
s := new(big.Int).SetBytes(sig_s)
|
sParam := new(big.Int).SetBytes(sig_s)
|
||||||
sequence := ecdsaSig{r, s}
|
sequence := ecdsaSig{rParam, sParam}
|
||||||
sigEncoded, err := asn1.Marshal(sequence)
|
sigEncoded, err := asn1.Marshal(sequence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshalling ecdsa signature: %w", err)
|
return fmt.Errorf("marshalling ecdsa signature: %w", err)
|
||||||
|
@ -213,7 +165,7 @@ func validateSNPReport(cert *x509.Certificate, expectedIdKeyDigest []byte, enfor
|
||||||
return &idkeyError{report.IdKeyDigest[:]}
|
return &idkeyError{report.IdKeyDigest[:]}
|
||||||
}
|
}
|
||||||
if log != nil {
|
if log != nil {
|
||||||
log.Warnf("Encountered different than configured idkeydigest value: %x.\n", report.IdKeyDigest[:])
|
log.Warnf("Encountered different than configured idkeydigest value: %x", report.IdKeyDigest[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +228,54 @@ type HCLAkValidator interface {
|
||||||
validateAk(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
validateAk(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type signatureError struct {
|
||||||
|
innerError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *signatureError) Unwrap() error {
|
||||||
|
return e.innerError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *signatureError) Error() string {
|
||||||
|
return fmt.Sprintf("signature validation failed: %v", e.innerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
type askError struct {
|
||||||
|
innerError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *askError) Unwrap() error {
|
||||||
|
return e.innerError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *askError) Error() string {
|
||||||
|
return fmt.Sprintf("validating ASK: %v", e.innerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
type vcekError struct {
|
||||||
|
innerError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *vcekError) Unwrap() error {
|
||||||
|
return e.innerError
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
type snpSignature struct {
|
type snpSignature struct {
|
||||||
R [72]byte
|
R [72]byte
|
||||||
S [72]byte
|
S [72]byte
|
|
@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package azure
|
package snp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -145,7 +145,7 @@ func TestTrustedKeyFromSNP(t *testing.T) {
|
||||||
idkeydigest, err := hex.DecodeString(tc.idkeydigest)
|
idkeydigest, err := hex.DecodeString(tc.idkeydigest)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
key, err := trustedKeyFromSNP(&instanceInfo, idkeydigest, tc.enforceIdKeyDigest, nil)(akPub, statement)
|
key, err := getTrustedKey(&instanceInfo, idkeydigest, tc.enforceIdKeyDigest, nil)(akPub, statement)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
tc.assertCorrectError(err)
|
tc.assertCorrectError(err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,7 +171,7 @@ func TestValidateAzureCVM(t *testing.T) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
err := validateAzureCVM(tc.attDoc)
|
err := validateCVM(tc.attDoc)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
} else {
|
} else {
|
37
internal/attestation/azure/trustedlaunch/issuer.go
Normal file
37
internal/attestation/azure/trustedlaunch/issuer.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package trustedlaunch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/internal/oid"
|
||||||
|
tpmclient "github.com/google/go-tpm-tools/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Issuer for Azure trusted launch TPM attestation.
|
||||||
|
type Issuer struct {
|
||||||
|
oid.AzureTrustedLaunch
|
||||||
|
*vtpm.Issuer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIssuer initializes a new Azure Issuer.
|
||||||
|
func NewIssuer() *Issuer {
|
||||||
|
return &Issuer{
|
||||||
|
Issuer: vtpm.NewIssuer(
|
||||||
|
vtpm.OpenVTPM,
|
||||||
|
tpmclient.AttestationKeyRSA,
|
||||||
|
getAttestation,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAttestation returns nil.
|
||||||
|
func getAttestation(tpm io.ReadWriteCloser) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
46
internal/attestation/azure/trustedlaunch/issuer_test.go
Normal file
46
internal/attestation/azure/trustedlaunch/issuer_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package trustedlaunch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/simulator"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSNPAttestation(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
tpmFunc vtpm.TPMOpenFunc
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
tpmFunc: simulator.OpenSimulatedTPM,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
tpm, err := tc.tpmFunc()
|
||||||
|
require.NoError(err)
|
||||||
|
defer tpm.Close()
|
||||||
|
|
||||||
|
_, err = getAttestation(tpm)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
49
internal/attestation/azure/trustedlaunch/validator.go
Normal file
49
internal/attestation/azure/trustedlaunch/validator.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package trustedlaunch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/internal/oid"
|
||||||
|
"github.com/google/go-tpm/tpm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validator for Azure trusted launch VM attestation.
|
||||||
|
type Validator struct {
|
||||||
|
oid.AzureTrustedLaunch
|
||||||
|
*vtpm.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewValidator initializes a new Azure validator with the provided PCR values.
|
||||||
|
func NewValidator(pcrs map[uint32][]byte, enforcedPCRs []uint32, log vtpm.WarnLogger) *Validator {
|
||||||
|
return &Validator{
|
||||||
|
Validator: vtpm.NewValidator(
|
||||||
|
pcrs,
|
||||||
|
enforcedPCRs,
|
||||||
|
trustedKey,
|
||||||
|
validateVM,
|
||||||
|
vtpm.VerifyPKCS1v15,
|
||||||
|
log,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trustedKey returns the key encoded in the given TPMT_PUBLIC message.
|
||||||
|
func trustedKey(akPub, instanceInfo []byte) (crypto.PublicKey, error) {
|
||||||
|
pubArea, err := tpm2.DecodePublic(akPub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pubArea.Key()
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateVM returns nil.
|
||||||
|
func validateVM(attestation vtpm.AttestationDocument) error {
|
||||||
|
return nil
|
||||||
|
}
|
81
internal/attestation/azure/trustedlaunch/validator_test.go
Normal file
81
internal/attestation/azure/trustedlaunch/validator_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package trustedlaunch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/simulator"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
||||||
|
"github.com/google/go-tpm-tools/client"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrustedKeyFromSNP(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
tpm, err := simulator.OpenSimulatedTPM()
|
||||||
|
require.NoError(err)
|
||||||
|
defer tpm.Close()
|
||||||
|
key, err := client.AttestationKeyRSA(tpm)
|
||||||
|
require.NoError(err)
|
||||||
|
defer key.Close()
|
||||||
|
akPub, err := key.PublicArea().Encode()
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
key []byte
|
||||||
|
instanceInfo []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
key: akPub,
|
||||||
|
instanceInfo: []byte{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
key, err := trustedKey(tc.key, tc.instanceInfo)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.NotNil(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAzureCVM(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
attDoc vtpm.AttestationDocument
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
attDoc: vtpm.AttestationDocument{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
err := validateVM(tc.attDoc)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
//go:build gcp
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright (c) Edgeless Systems GmbH
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package gcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/goleak"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
goleak.VerifyTestMain(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttestation(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(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, nil)
|
|
||||||
|
|
||||||
nonce := []byte{2, 3, 4}
|
|
||||||
challenge := []byte("Constellation")
|
|
||||||
|
|
||||||
attDocRaw, err := issuer.Issue(challenge, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
var attDoc vtpm.AttestationDocument
|
|
||||||
err = json.Unmarshal(attDocRaw, &attDoc)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.Equal(challenge, attDoc.UserData)
|
|
||||||
originalPCR := attDoc.Attestation.Quotes[1].Pcrs.Pcrs[uint32(vtpm.PCRIndexOwnerID)]
|
|
||||||
|
|
||||||
out, err := validator.Validate(attDocRaw, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(challenge, out)
|
|
||||||
|
|
||||||
// Mark node as intialized. We should still be abe to validate
|
|
||||||
assert.NoError(vtpm.MarkNodeAsBootstrapped(vtpm.OpenVTPM, []byte("Test")))
|
|
||||||
|
|
||||||
attDocRaw, err = issuer.Issue(challenge, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// Make sure the PCR changed
|
|
||||||
err = json.Unmarshal(attDocRaw, &attDoc)
|
|
||||||
require.NoError(err)
|
|
||||||
assert.NotEqual(originalPCR, attDoc.Attestation.Quotes[1].Pcrs.Pcrs[uint32(vtpm.PCRIndexOwnerID)])
|
|
||||||
|
|
||||||
out, err = validator.Validate(attDocRaw, nonce)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(challenge, out)
|
|
||||||
}
|
|
33
internal/cloud/vmtype/vmtype.go
Normal file
33
internal/cloud/vmtype/vmtype.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vmtype
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
//go:generate stringer -type=VMType
|
||||||
|
|
||||||
|
// VMType describes different vm types we support. Introduced for Azure SNP / Trusted Launch attestation.
|
||||||
|
type VMType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown VMType = iota
|
||||||
|
AzureCVM
|
||||||
|
AzureTrustedLaunch
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromString returns a VMType from a string.
|
||||||
|
func FromString(s string) VMType {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
switch s {
|
||||||
|
case "azurecvm":
|
||||||
|
return AzureCVM
|
||||||
|
case "azuretrustedlaunch":
|
||||||
|
return AzureTrustedLaunch
|
||||||
|
default:
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
}
|
25
internal/cloud/vmtype/vmtype_string.go
Normal file
25
internal/cloud/vmtype/vmtype_string.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Code generated by "stringer -type=VMType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package vmtype
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Unknown-0]
|
||||||
|
_ = x[AzureCVM-1]
|
||||||
|
_ = x[AzureTrustedLaunch-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _VMType_name = "UnknownAzureCVMAzureTrustedLaunch"
|
||||||
|
|
||||||
|
var _VMType_index = [...]uint8{0, 7, 15, 33}
|
||||||
|
|
||||||
|
func (i VMType) String() string {
|
||||||
|
if i >= VMType(len(_VMType_index)-1) {
|
||||||
|
return "VMType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _VMType_name[_VMType_index[i]:_VMType_index[i+1]]
|
||||||
|
}
|
|
@ -487,6 +487,10 @@ func (c *Config) IsAzureNonCVM() bool {
|
||||||
return c.Provider.Azure != nil && c.Provider.Azure.ConfidentialVM != nil && !*c.Provider.Azure.ConfidentialVM
|
return c.Provider.Azure != nil && c.Provider.Azure.ConfidentialVM != nil && !*c.Provider.Azure.ConfidentialVM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) EnforcesIdKeyDigest() bool {
|
||||||
|
return c.Provider.Azure != nil && c.Provider.Azure.EnforceIdKeyDigest != nil && *c.Provider.Azure.EnforceIdKeyDigest
|
||||||
|
}
|
||||||
|
|
||||||
// FromFile returns config file with `name` read from `fileHandler` by parsing
|
// FromFile returns config file with `name` read from `fileHandler` by parsing
|
||||||
// it as YAML.
|
// it as YAML.
|
||||||
func FromFile(fileHandler file.Handler, name string) (*Config, error) {
|
func FromFile(fileHandler file.Handler, name string) (*Config, error) {
|
||||||
|
|
|
@ -84,6 +84,8 @@ const (
|
||||||
IdKeyDigestFilename = "idkeydigest"
|
IdKeyDigestFilename = "idkeydigest"
|
||||||
// EnforceIdKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
|
// EnforceIdKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
|
||||||
EnforceIdKeyDigestFilename = "enforceIdKeyDigest"
|
EnforceIdKeyDigestFilename = "enforceIdKeyDigest"
|
||||||
|
// AzureCVM is the name of the file indicating whether the cluster is expected to run on CVMs or not.
|
||||||
|
AzureCVM = "azureCVM"
|
||||||
// K8sVersion is the filename of the mapped "k8s-version" configMap file.
|
// K8sVersion is the filename of the mapped "k8s-version" configMap file.
|
||||||
K8sVersion = "k8s-version"
|
K8sVersion = "k8s-version"
|
||||||
|
|
||||||
|
@ -101,6 +103,7 @@ const (
|
||||||
KubernetesJoinTokenTTL = 15 * time.Minute
|
KubernetesJoinTokenTTL = 15 * time.Minute
|
||||||
ConstellationNamespace = "kube-system"
|
ConstellationNamespace = "kube-system"
|
||||||
JoinConfigMap = "join-config"
|
JoinConfigMap = "join-config"
|
||||||
|
InternalConfigMap = "internal-config"
|
||||||
|
|
||||||
//
|
//
|
||||||
// Helm.
|
// Helm.
|
||||||
|
|
|
@ -2,8 +2,23 @@
|
||||||
Copyright (c) Edgeless Systems GmbH
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
|
||||||
|
|
||||||
|
Package oid defines OIDs for different CSPs. Currently this is used in attested TLS to distinguish the attestation documents.
|
||||||
|
OIDs beginning with 1.3.9900 are reserved and can be used without registration.
|
||||||
|
|
||||||
|
* The 1.3.9900.1 branch is reserved for placeholder values and testing.
|
||||||
|
|
||||||
|
* The 1.3.9900.2 branch is reserved for AWS.
|
||||||
|
|
||||||
|
* The 1.3.9900.3 branch is reserved for GCP.
|
||||||
|
|
||||||
|
* The 1.3.9900.4 branch is reserved for Azure.
|
||||||
|
|
||||||
|
* The 1.3.9900.5 branch is reserved for QEMU.
|
||||||
|
|
||||||
|
Deprecated OIDs should never be reused for different purposes.
|
||||||
|
Instead, new OIDs should be added in the appropriate branch at the next available index.
|
||||||
|
*/
|
||||||
package oid
|
package oid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,15 +30,12 @@ type Getter interface {
|
||||||
OID() asn1.ObjectIdentifier
|
OID() asn1.ObjectIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we define OIDs for different CSPs. Currently this is used in attested TLS to distinguish the attestation documents.
|
|
||||||
// OIDs beginning with 1.3.9900 are reserved and can be used without registration.
|
|
||||||
|
|
||||||
// Dummy OID for testing.
|
// Dummy OID for testing.
|
||||||
type Dummy struct{}
|
type Dummy struct{}
|
||||||
|
|
||||||
// OID returns the struct's object identifier.
|
// OID returns the struct's object identifier.
|
||||||
func (Dummy) OID() asn1.ObjectIdentifier {
|
func (Dummy) OID() asn1.ObjectIdentifier {
|
||||||
return asn1.ObjectIdentifier{1, 3, 9900, 1}
|
return asn1.ObjectIdentifier{1, 3, 9900, 1, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWS holds the AWS OID.
|
// AWS holds the AWS OID.
|
||||||
|
@ -31,7 +43,7 @@ type AWS struct{}
|
||||||
|
|
||||||
// OID returns the struct's object identifier.
|
// OID returns the struct's object identifier.
|
||||||
func (AWS) OID() asn1.ObjectIdentifier {
|
func (AWS) OID() asn1.ObjectIdentifier {
|
||||||
return asn1.ObjectIdentifier{1, 3, 9900, 2}
|
return asn1.ObjectIdentifier{1, 3, 9900, 2, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCP holds the GCP OID.
|
// GCP holds the GCP OID.
|
||||||
|
@ -39,15 +51,23 @@ type GCP struct{}
|
||||||
|
|
||||||
// OID returns the struct's object identifier.
|
// OID returns the struct's object identifier.
|
||||||
func (GCP) OID() asn1.ObjectIdentifier {
|
func (GCP) OID() asn1.ObjectIdentifier {
|
||||||
return asn1.ObjectIdentifier{1, 3, 9900, 3}
|
return asn1.ObjectIdentifier{1, 3, 9900, 3, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Azure holds the Azure OID.
|
// AzureSNP holds the OID for Azure SNP CVMs.
|
||||||
type Azure struct{}
|
type AzureSNP struct{}
|
||||||
|
|
||||||
// OID returns the struct's object identifier.
|
// OID returns the struct's object identifier.
|
||||||
func (Azure) OID() asn1.ObjectIdentifier {
|
func (AzureSNP) OID() asn1.ObjectIdentifier {
|
||||||
return asn1.ObjectIdentifier{1, 3, 9900, 4}
|
return asn1.ObjectIdentifier{1, 3, 9900, 4, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Azure holds the OID for Azure TrustedLaunch VMs.
|
||||||
|
type AzureTrustedLaunch struct{}
|
||||||
|
|
||||||
|
// OID returns the struct's object identifier.
|
||||||
|
func (AzureTrustedLaunch) OID() asn1.ObjectIdentifier {
|
||||||
|
return asn1.ObjectIdentifier{1, 3, 9900, 4, 2}
|
||||||
}
|
}
|
||||||
|
|
||||||
// QEMU holds the QEMU OID.
|
// QEMU holds the QEMU OID.
|
||||||
|
@ -55,5 +75,5 @@ type QEMU struct{}
|
||||||
|
|
||||||
// OID returns the struct's object identifier.
|
// OID returns the struct's object identifier.
|
||||||
func (QEMU) OID() asn1.ObjectIdentifier {
|
func (QEMU) OID() asn1.ObjectIdentifier {
|
||||||
return asn1.ObjectIdentifier{1, 3, 9900, 5}
|
return asn1.ObjectIdentifier{1, 3, 9900, 5, 1}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,10 +36,10 @@ func IsSupportedK8sVersion(version string) bool {
|
||||||
const (
|
const (
|
||||||
// Constellation images.
|
// Constellation images.
|
||||||
// These images are built in a way that they support all versions currently listed in VersionConfigs.
|
// 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-0.20220831112436-10766c6049b8"
|
JoinImage = "ghcr.io/edgelesssys/constellation/join-service:v0.0.2-0.20220901132202-b08ce24b6b40"
|
||||||
AccessManagerImage = "ghcr.io/edgelesssys/constellation/access-manager:v0.0.1"
|
AccessManagerImage = "ghcr.io/edgelesssys/constellation/access-manager:v0.0.1"
|
||||||
KmsImage = "ghcr.io/edgelesssys/constellation/kmsserver:v0.0.1"
|
KmsImage = "ghcr.io/edgelesssys/constellation/kmsserver:v0.0.2-0.20220901132202-b08ce24b6b40"
|
||||||
VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v0.0.1-0.20220831112436-10766c6049b8"
|
VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v0.0.2-0.20220901132202-b08ce24b6b40"
|
||||||
GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:20220713.00"
|
GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:20220713.00"
|
||||||
NodeOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-operator-catalog"
|
NodeOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-operator-catalog"
|
||||||
NodeOperatorVersion = "v0.0.1"
|
NodeOperatorVersion = "v0.0.1"
|
||||||
|
|
|
@ -15,7 +15,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/internal/atls"
|
"github.com/edgelesssys/constellation/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/azure"
|
"github.com/edgelesssys/constellation/internal/attestation/azure/snp"
|
||||||
|
"github.com/edgelesssys/constellation/internal/attestation/azure/trustedlaunch"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
"github.com/edgelesssys/constellation/internal/attestation/gcp"
|
||||||
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
"github.com/edgelesssys/constellation/internal/attestation/qemu"
|
||||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||||
|
@ -31,16 +32,23 @@ type Updatable struct {
|
||||||
newValidator newValidatorFunc
|
newValidator newValidatorFunc
|
||||||
fileHandler file.Handler
|
fileHandler file.Handler
|
||||||
csp cloudprovider.Provider
|
csp cloudprovider.Provider
|
||||||
|
azureCVM bool
|
||||||
atls.Validator
|
atls.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewValidator initializes a new updatable validator.
|
// NewValidator initializes a new updatable validator.
|
||||||
func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler) (*Updatable, error) {
|
func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler, azureCVM bool) (*Updatable, error) {
|
||||||
var newValidator newValidatorFunc
|
var newValidator newValidatorFunc
|
||||||
switch cloudprovider.FromString(csp) {
|
switch cloudprovider.FromString(csp) {
|
||||||
case cloudprovider.Azure:
|
case cloudprovider.Azure:
|
||||||
|
if azureCVM {
|
||||||
newValidator = func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator {
|
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)
|
return snp.NewValidator(m, e, idkeydigest, enforceIdKeyDigest, log)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newValidator = func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator {
|
||||||
|
return trustedlaunch.NewValidator(m, e, log)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
newValidator = func(m map[uint32][]byte, e []uint32, _ []byte, _ bool, log *logger.Logger) atls.Validator {
|
newValidator = func(m map[uint32][]byte, e []uint32, _ []byte, _ bool, log *logger.Logger) atls.Validator {
|
||||||
|
@ -59,6 +67,7 @@ func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler) (*Up
|
||||||
newValidator: newValidator,
|
newValidator: newValidator,
|
||||||
fileHandler: fileHandler,
|
fileHandler: fileHandler,
|
||||||
csp: cloudprovider.FromString(csp),
|
csp: cloudprovider.FromString(csp),
|
||||||
|
azureCVM: azureCVM,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.Update(); err != nil {
|
if err := u.Update(); err != nil {
|
||||||
|
@ -100,7 +109,7 @@ func (u *Updatable) Update() error {
|
||||||
|
|
||||||
var idkeydigest []byte
|
var idkeydigest []byte
|
||||||
var enforceIdKeyDigest bool
|
var enforceIdKeyDigest bool
|
||||||
if u.csp == cloudprovider.Azure {
|
if u.csp == cloudprovider.Azure && u.azureCVM {
|
||||||
u.log.Infof("Updating encforceIdKeyDigest value")
|
u.log.Infof("Updating encforceIdKeyDigest value")
|
||||||
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename))
|
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -91,12 +91,17 @@ func TestNewUpdateableValidator(t *testing.T) {
|
||||||
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
||||||
[]byte("false"),
|
[]byte("false"),
|
||||||
))
|
))
|
||||||
|
require.NoError(handler.Write(
|
||||||
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
||||||
|
[]byte("true"),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := NewValidator(
|
_, err := NewValidator(
|
||||||
logger.NewTest(t),
|
logger.NewTest(t),
|
||||||
tc.provider,
|
tc.provider,
|
||||||
handler,
|
handler,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
@ -147,6 +152,10 @@ func TestUpdate(t *testing.T) {
|
||||||
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
||||||
[]byte("false"),
|
[]byte("false"),
|
||||||
))
|
))
|
||||||
|
require.NoError(handler.Write(
|
||||||
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
||||||
|
[]byte("true"),
|
||||||
|
))
|
||||||
|
|
||||||
// call update once to initialize the server's validator
|
// call update once to initialize the server's validator
|
||||||
require.NoError(validator.Update())
|
require.NoError(validator.Update())
|
||||||
|
@ -213,6 +222,10 @@ func TestUpdateConcurrency(t *testing.T) {
|
||||||
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIdKeyDigestFilename),
|
||||||
[]byte("false"),
|
[]byte("false"),
|
||||||
))
|
))
|
||||||
|
require.NoError(handler.Write(
|
||||||
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
||||||
|
[]byte("true"),
|
||||||
|
))
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,16 @@ func main() {
|
||||||
|
|
||||||
handler := file.NewHandler(afero.NewOsFs())
|
handler := file.NewHandler(afero.NewOsFs())
|
||||||
|
|
||||||
validator, err := watcher.NewValidator(log.Named("validator"), *provider, handler)
|
cvmRaw, err := handler.Read(filepath.Join(constants.ServiceBasePath, constants.AzureCVM))
|
||||||
|
if err != nil {
|
||||||
|
log.With(zap.Error(err)).Fatalf("Failed to get azureCVM from config map")
|
||||||
|
}
|
||||||
|
azureCVM, err := strconv.ParseBool(string(cvmRaw))
|
||||||
|
if err != nil {
|
||||||
|
log.With(zap.Error(err)).Fatalf("Failed to parse content of AzureCVM: %s", cvmRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
validator, err := watcher.NewValidator(log.Named("validator"), *provider, handler, azureCVM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
log.With(zap.Error(err)).Fatalf("Failed to create validator")
|
log.With(zap.Error(err)).Fatalf("Failed to create validator")
|
||||||
|
|
|
@ -67,6 +67,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.With(zap.Error).Fatalf("Failed to create Azure metadata API")
|
log.With(zap.Error).Fatalf("Failed to create Azure metadata API")
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer = azure.NewIssuer()
|
issuer = azure.NewIssuer()
|
||||||
|
|
||||||
case "gcp":
|
case "gcp":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue