diff --git a/go.mod b/go.mod index f3d1d09..a1c6237 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,21 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.14 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect diff --git a/go.sum b/go.sum index eed3c27..af0a359 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,36 @@ github.com/GoogleCloudPlatform/confidential-space/server v0.0.0-20260307011055-8 github.com/GoogleCloudPlatform/confidential-space/server v0.0.0-20260307011055-895ec9019dd7/go.mod h1:sNFt/HcARjGxR3/2s7hwlqvHlUzXdaCiS62u7A4rnHg= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.14 h1:opVIRo/ZbbI8OIqSOKmpFaY7IwfFUOCCXBsUpJOwDdI= +github.com/aws/aws-sdk-go-v2/config v1.32.14/go.mod h1:U4/V0uKxh0Tl5sxmCBZ3AecYny4UNlVmObYjKuuaiOo= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.2 h1:Ytu50ChAxCiDsOlBcBq8jbczXy6+QLb07T65DBJASRs= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.2/go.mod h1:R+2BNtUfTfhPY0RH18oL02q116bakeBWjanrbnVBqkM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -126,4 +156,4 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 34437e1..29d3820 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -26,6 +26,8 @@ import ( "strings" "sync" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/google/go-attestation/attest" nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/agent/nodeattestor/v1" configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" @@ -48,9 +50,15 @@ type Plugin struct { type Config struct { trustDomain string + AWS AWSConfig `hcl:"aws"` PVE PVEConfig `hcl:"pve"` } +type AWSConfig struct { + Enabled bool `hcl:"enabled"` + DiscoveryMethod string `hcl:"discovery_method"` // "", "smbios" or "metadata" +} + type PVEConfig struct { Enabled bool `hcl:"enabled"` } @@ -215,6 +223,29 @@ func (p *Plugin) generateAttestationData(ctx context.Context) (*common.Attestati } conf := p.getConfig() + if conf.AWS.Enabled { + data.AWS = &common.AWSInstanceData{} + method := strings.ToLower(conf.AWS.DiscoveryMethod) + if method == "" { + method = "smbios" + } + + switch method { + case "metadata": + cfg, err := config.LoadDefaultConfig(ctx) + if err == nil { + imdsClient := imds.NewFromConfig(cfg) + output, err := imdsClient.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{}) + if err == nil && output.InstanceIdentityDocument.InstanceID != "" { + data.AWS.InstanceID = output.InstanceIdentityDocument.InstanceID + } + } + case "smbios": + data.AWS.InstanceID = p.getAWSInstanceIDFromSMBIOS() + default: + return nil, nil, fmt.Errorf("bad method") + } + } if conf.PVE.Enabled { data.PVE = &common.PVEInstanceData{ @@ -227,6 +258,14 @@ func (p *Plugin) generateAttestationData(ctx context.Context) (*common.Attestati return data, aikBytes, nil } +func (p *Plugin) getAWSInstanceIDFromSMBIOS() string { + data, err := os.ReadFile("/sys/devices/virtual/dmi/id/board_asset_tag") + if err != nil { + return "" + } + return strings.TrimSpace(string(data)) +} + func (p *Plugin) getPVEVMIDFromSMBIOS() int32 { data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_sku") if err != nil { diff --git a/pkg/common/tpm_attestor.go b/pkg/common/tpm_attestor.go index aad5992..696727c 100644 --- a/pkg/common/tpm_attestor.go +++ b/pkg/common/tpm_attestor.go @@ -36,9 +36,14 @@ const ( type AttestationData struct { EK []byte AK *attest.AttestationParameters + AWS *AWSInstanceData PVE *PVEInstanceData } +type AWSInstanceData struct { + InstanceID string `json:"instance_id"` +} + type PVEInstanceData struct { CUID string `json:"cuid"` UUID string `json:"uuid"` diff --git a/pkg/server/server.go b/pkg/server/server.go index 8e341be..aa95517 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "crypto/x509" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -27,6 +28,9 @@ import ( "path/filepath" "sync" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/bloomberg/spire-tpm-plugin/pkg/common" "github.com/google/go-attestation/attest" x509ext "github.com/google/go-attestation/x509" @@ -43,9 +47,15 @@ type Config struct { trustDomain string CaPath string `hcl:"ca_path"` HashPath string `hcl:"hash_path"` + AWS AWSConfig `hcl:"aws"` PVE PVEGlobalConfig `hcl:"pve"` } +type AWSConfig struct { + Enabled bool `hcl:"enabled"` + HashPath string `hcl:"hash_path"` +} + // Plugin implements the nodeattestor Plugin interface type Plugin struct { nodeattestorv1.UnsafeNodeAttestorServer @@ -162,7 +172,22 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { var selectors []string caCheck := false validEK := false - if p.config.PVE.Enabled && attestationData.PVE != nil { + if p.config.AWS.Enabled && attestationData.AWS != nil { + if attestationData.AWS.InstanceID == "" { + return fmt.Errorf("tpm: bad aws data") + } + pubBytes, _ := x509.MarshalPKIXPublicKey(ek.Public) + awsSelectors, err := p.verifyAWSTPM(stream.Context(), attestationData.AWS.InstanceID, pubBytes) + if err == nil { + selectors = append(selectors, awsSelectors...) + + if p.config.AWS.HashPath != "" { + validEK = checkHashAllowed(p.config.AWS.HashPath, hashEncoded) + } else { + validEK = true + } + } + } else if p.config.PVE.Enabled && attestationData.PVE != nil { clusterConf, ok := p.config.PVE.Clusters[attestationData.PVE.CUID] hashPath := p.config.PVE.Clusters[attestationData.PVE.CUID].HashPath if !ok { @@ -304,6 +329,27 @@ func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { }) } +func (p *Plugin) verifyAWSTPM(ctx context.Context, instanceID string, ekPub []byte) ([]string, error) { + cfg, err := config.LoadDefaultConfig(ctx) + if err != nil { + return nil, err + } + client := ec2.NewFromConfig(cfg) + out, err := client.GetInstanceTpmEkPub(ctx, &ec2.GetInstanceTpmEkPubInput{ + InstanceId: aws.String(instanceID), + KeyFormat: "der", + KeyType: "rsa-2048", + }) + if err != nil { + return nil, err + } + decodedAWSKey, _ := base64.StdEncoding.DecodeString(*out.KeyValue) + if !bytes.Equal(decodedAWSKey, ekPub) { + return nil, errors.New("EK mismatch") + } + return []string{"aws:instance_id:" + instanceID}, nil +} + func checkHashAllowed(hashPath, hashEncoded string) bool { // Check if hashPath is a directory, fail if this is simply a file fileInfo, err := os.Stat(hashPath)