mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-06 05:54:28 -04:00
Create hack folder with independent modules (#131)
This commit is contained in:
parent
cfad36720b
commit
8e0f9491af
12 changed files with 1810 additions and 11 deletions
32
hack/clidocgen/main.go
Normal file
32
hack/clidocgen/main.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Clidocgen generates a Markdown page describing all CLI commands.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/cmd"
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rootCmd := cmd.NewRootCmd()
|
||||
rootCmd.DisableAutoGenTag = true
|
||||
|
||||
// Generate Markdown for all commands.
|
||||
cmdList := &bytes.Buffer{}
|
||||
body := &bytes.Buffer{}
|
||||
for _, c := range rootCmd.Commands() {
|
||||
name := c.Name()
|
||||
fmt.Fprintf(cmdList, "* [%v](#constellation-%v): %v\n", name, name, c.Short)
|
||||
if err := doc.GenMarkdown(c, body); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove "see also" sections. They list parent and child commands, which is not interesting for us.
|
||||
cleanedBody := regexp.MustCompile(`(?s)### SEE ALSO\n.+?\n\n`).ReplaceAll(body.Bytes(), nil)
|
||||
|
||||
fmt.Printf("Commands:\n\n%s\n%s", cmdList, cleanedBody)
|
||||
}
|
146
hack/go.mod
Normal file
146
hack/go.mod
Normal file
|
@ -0,0 +1,146 @@
|
|||
module github.com/edgelesssys/constellation/hack
|
||||
|
||||
go 1.18
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.24.0
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.24.0
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.24.0
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.24.0
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.24.0
|
||||
k8s.io/client-go => k8s.io/client-go v0.24.0
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.24.0
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.24.0
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.24.0
|
||||
k8s.io/component-base => k8s.io/component-base v0.24.0
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.24.0
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.24.0
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.24.0
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.24.0
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.24.0
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.24.0
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.24.0
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.24.0
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.24.0
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.24.0
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.24.0
|
||||
k8s.io/metrics => k8s.io/metrics v0.24.0
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.24.0
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.24.0
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.24.0
|
||||
k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.24.0
|
||||
k8s.io/sample-controller => k8s.io/sample-controller v0.24.0
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/edgelesssys/constellation => ./..
|
||||
github.com/nmiculinic/wg-quick-go v0.1.3 => github.com/katexochen/wg-quick-go v0.1.3-beta.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/edgelesssys/constellation v1.0.0
|
||||
github.com/google/go-tpm-tools v0.3.8
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
google.golang.org/grpc v1.46.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.100.2 // indirect
|
||||
cloud.google.com/go/compute v1.5.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go/kms v1.4.0 // indirect
|
||||
cloud.google.com/go/resourcemanager v1.2.0 // indirect
|
||||
cloud.google.com/go/storage v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v62.2.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.3.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.11.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.32.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.16.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 // indirect
|
||||
github.com/aws/smithy-go v1.11.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2 // indirect
|
||||
github.com/google/go-attestation v0.4.4-0.20220404204839-8820d49b18d9 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/google/go-tpm v0.3.3 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/tink/go v1.6.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/nmiculinic/wg-quick-go v0.1.3 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 // indirect
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68 // indirect
|
||||
google.golang.org/api v0.73.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
1649
hack/go.sum
Normal file
1649
hack/go.sum
Normal file
File diff suppressed because it is too large
Load diff
119
hack/pcr-reader/README.md
Normal file
119
hack/pcr-reader/README.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
# PCR-updater
|
||||
|
||||
New images result in different PCR values for the image.
|
||||
This utility program makes it simple to update the expected PCR values of the CLI.
|
||||
|
||||
## Usage
|
||||
|
||||
### Script
|
||||
|
||||
Run `fetch_pcrs.sh` to create Constellations on all supported cloud providers and read their PCR states.
|
||||
```shell
|
||||
./fetch_pcrs.sh
|
||||
```
|
||||
|
||||
The result is printed to screen and written as Go code to files in `./pcrs`.
|
||||
```bash
|
||||
+ main
|
||||
+ command -v constellation
|
||||
+ command -v go
|
||||
+ mkdir -p ./pcrs
|
||||
+ constellation create azure 2 Standard_D4s_v3 --name pcr-fetch -y
|
||||
Your Constellation was created successfully.
|
||||
++ jq '.azurecoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json
|
||||
+ coord_ip=192.0.2.1
|
||||
+ go run ../main.go -coord-ip 192.0.2.1 -o ./pcrs/azure_pcrs.go
|
||||
connecting to Coordinator at 192.0.2.1:9000
|
||||
PCRs:
|
||||
{
|
||||
"0": "q27iAZeXGAiCPdu1bqRA2gAoyMO2KrXWY4YkTCQowc4=",
|
||||
...
|
||||
"9": "dEGJtQe3h+SI0z42yO7TklzwPixtM3iMCUeJPGRozvg="
|
||||
}
|
||||
+ constellation terminate
|
||||
Your Constellation was terminated successfully.
|
||||
+ constellation create gcp 2 n2d-standard-2 --name pcr-fetch -y
|
||||
Your Constellation was created successfully.
|
||||
++ jq '.gcpcoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json
|
||||
+ coord_ip=192.0.2.2
|
||||
+ go run ../main.go -coord-ip 192.0.2.2 -o ./pcrs/gcp_pcrs.go
|
||||
connecting to Coordinator at 192.0.2.2:9000
|
||||
PCRs:
|
||||
{
|
||||
"0": "DzXCFGCNk8em5ornNZtKi+Wg6Z7qkQfs5CfE3qTkOc8=",
|
||||
...
|
||||
"9": "gse53SjsqREEdOpImJH4KAb0b8PqIgwI+Ps/XSiFnN4="
|
||||
}
|
||||
+ constellation terminate
|
||||
Your Constellation was terminated successfully.
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
To read the PCR state of any running Constellation node, run the following:
|
||||
```shell
|
||||
go run main.go -coord-ip <NODE_IP> -coord-port <COORDINATOR_PORT>
|
||||
```
|
||||
|
||||
The output is similar to the following:
|
||||
```shell
|
||||
$ go run main.go -coord-ip 192.0.2.3 -coord-port 12345
|
||||
connecting to Coordinator at 192.0.2.3:12345
|
||||
PCRs:
|
||||
{
|
||||
"0": "DzXCFGCNk8em5ornNZtKi+Wg6Z7qkQfs5CfE3qTkOc8=",
|
||||
"1": "XBoRlWuQx6nIDr5vgUL0DlJHy6H6u1dPU3qK2NyToc8=",
|
||||
"10": "WLmYFRmDft/ajZJ056CAhpheU6Vbt73aR8eIQpLRGq0=",
|
||||
"11": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"12": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"13": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"14": "4tPyJd6A5g09KduV3+nWZQCiEzHAiRT5DulmAqlvpZU=",
|
||||
"15": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"16": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"17": "//////////////////////////////////////////8=",
|
||||
"18": "//////////////////////////////////////////8=",
|
||||
"19": "//////////////////////////////////////////8=",
|
||||
"2": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||
"20": "//////////////////////////////////////////8=",
|
||||
"21": "//////////////////////////////////////////8=",
|
||||
"22": "//////////////////////////////////////////8=",
|
||||
"23": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"3": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||
"4": "MmkueFj1rP2seH+bjeIRsO4dUnLnMdl7QgtGoAtQH7M=",
|
||||
"5": "ExaiapuIfo0KMBo8wj6kPDORLocgnH1C0G/KY8DcV3A=",
|
||||
"6": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||
"7": "UZcW+fhFRMpFkgU+EfKG2s3KdmgEA+TD2quLmthQHbo=",
|
||||
"8": "KLSMootYaHBjysWKq9CAYXkXpeYx9PUBimlSEZGJqUM=",
|
||||
"9": "gse53SjsqREEdOpImJH4KAb0b8PqIgwI+Ps/XSiFnN4="
|
||||
}
|
||||
```
|
||||
|
||||
## Meaning of PCR values
|
||||
|
||||
An overview about what data is measured into the different registers can be found [in the TPM spec](https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf#%5B%7B%22num%22%3A157%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C33%2C400%2C0%5D).
|
||||
|
||||
We use the TPM and its PCRs to verify all nodes of a Constellation run with the same firmware and OS software.
|
||||
|
||||
### Azure trusted launch
|
||||
|
||||
PCR[0] measures the firmware volume (FV). Changes to FV also change PCR[0], making it unreliable for attestation.
|
||||
PCR[6] measures the VM ID. This is unusable for cluster attestation for two reasons:
|
||||
1. The Coordinator does not know the VM ID of nodes wanting to join the cluster, so it can not compute the expected PCR[6] for the joining VM
|
||||
2. A user may attest any node of the cluster without knowing the VM ID
|
||||
|
||||
PCR[10] is used by Linux Integrity Measurement Architecture (IMA).
|
||||
IMA creates runtime measurements based on a measurement policy (which is obsolete for Constellation, since we use dm-verity).
|
||||
The first entry of the runtime measurements is the `boot_aggregate`. It is a SHA1 hash over PCRs 0 to 7.
|
||||
As detailed earlier, PCR[6] is different for every VM in Azure, therefore PCR[10] will also be different since it includes PCR[6], meaning we can not use it for attestation.
|
||||
IMA writing its measurements into PCR[10] can not be disabled without rebuilding the kernel.
|
||||
|
||||
### Azure flexible deployment and attestation (FDA)
|
||||
|
||||
With FDA CVMs measuring all of the firmware, it should be possible to use all PCRs for attestation since we know, and can choose, what firmware is running.
|
||||
|
||||
### GCP confidential VM
|
||||
|
||||
GCP uses confidential VMs based on AMD SEV-ES with a vTPM interface.
|
||||
|
||||
PCR[0] contains the measurement of a string marking the VM as using ADM SEV-ES.
|
||||
All firmware measurements seem to be constant.
|
33
hack/pcr-reader/fetch_pcrs.sh
Executable file
33
hack/pcr-reader/fetch_pcrs.sh
Executable file
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -o xtrace
|
||||
trap 'terminate $?' ERR
|
||||
|
||||
terminate() {
|
||||
echo "error: $1"
|
||||
constellation terminate
|
||||
exit 1
|
||||
}
|
||||
|
||||
main() {
|
||||
command -v constellation > /dev/null
|
||||
command -v go > /dev/null
|
||||
command -v jq > /dev/null
|
||||
|
||||
mkdir -p ./pcrs
|
||||
|
||||
# Fetch Azure PCRs
|
||||
# TODO: Switch to confidential VMs
|
||||
constellation create azure 2 Standard_D4s_v3 --name pcr-fetch -y
|
||||
coord_ip=$(jq '.azurecoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json)
|
||||
go run main.go -coord-ip "${coord_ip}" -o ./pcrs/azure_pcrs.go
|
||||
constellation terminate
|
||||
|
||||
# Fetch GCP PCRs
|
||||
constellation create gcp 2 n2d-standard-2 --name pcr-fetch -y
|
||||
coord_ip=$(jq '.gcpcoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json)
|
||||
go run main.go -coord-ip "${coord_ip}" -o ./pcrs/gcp_pcrs.go
|
||||
constellation terminate
|
||||
}
|
||||
|
||||
main
|
176
hack/pcr-reader/main.go
Normal file
176
hack/pcr-reader/main.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/status"
|
||||
"github.com/edgelesssys/constellation/coordinator/atls"
|
||||
"github.com/edgelesssys/constellation/coordinator/attestation/azure"
|
||||
"github.com/edgelesssys/constellation/coordinator/attestation/gcp"
|
||||
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/coordinator/oid"
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/spf13/afero"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
coordIP = flag.String("coord-ip", "", "IP of the VM the Coordinator is running on")
|
||||
coordinatorPort = flag.String("coord-port", "9000", "Port of the Coordinator's pub API")
|
||||
export = flag.String("o", "", "Write PCRs, formatted as Go code, to file")
|
||||
quiet = flag.Bool("q", false, "Set to disable output")
|
||||
timeout = flag.Duration("timeout", 2*time.Minute, "Wait this duration for the Coordinator to become available")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
fmt.Printf("connecting to Coordinator at %s:%s\n", *coordIP, *coordinatorPort)
|
||||
addr := net.JoinHostPort(*coordIP, *coordinatorPort)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
||||
defer cancel()
|
||||
|
||||
// wait for coordinator to come online
|
||||
waiter := status.NewWaiter()
|
||||
if err := waiter.InitializeValidators([]atls.Validator{
|
||||
azure.NewValidator(map[uint32][]byte{}),
|
||||
gcp.NewValidator(map[uint32][]byte{}),
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := waiter.WaitFor(ctx, addr, state.AcceptingInit, state.ActivatingNodes, state.IsNode, state.NodeWaitingForClusterJoin); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
attDocRaw := []byte{}
|
||||
tlsConfig, err := atls.CreateUnverifiedClientTLSConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tlsConfig.VerifyPeerCertificate = getVerifyPeerCertificateFunc(&attDocRaw)
|
||||
if err := connectToCoordinator(ctx, addr, tlsConfig); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
pcrs, err := validatePCRAttDoc(attDocRaw)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
if err := printPCRs(os.Stdout, pcrs); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if *export != "" {
|
||||
if err := exportToFile(*export, pcrs, &afero.Afero{Fs: afero.NewOsFs()}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connectToCoordinator connects to the Constellation Coordinator and returns its attestation document.
|
||||
func connectToCoordinator(ctx context.Context, addr string, tlsConfig *tls.Config) error {
|
||||
conn, err := grpc.DialContext(
|
||||
ctx, addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pubproto.NewAPIClient(conn)
|
||||
_, err = client.GetState(ctx, &pubproto.GetStateRequest{})
|
||||
return err
|
||||
}
|
||||
|
||||
// getVerifyPeerCertificateFunc returns a VerifyPeerCertificate function, which writes the attestation document extension to the given byte slice pointer.
|
||||
func getVerifyPeerCertificateFunc(attDoc *[]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return errors.New("rawCerts is empty")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ex := range cert.Extensions {
|
||||
if ex.Id.Equal(oid.Azure{}.OID()) || ex.Id.Equal(oid.GCP{}.OID()) {
|
||||
if err := json.Unmarshal(ex.Value, attDoc); err != nil {
|
||||
*attDoc = ex.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(*attDoc) == 0 {
|
||||
return errors.New("did not receive attestation document in certificate extension")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// validatePCRAttDoc parses and validates PCRs of an attestation document.
|
||||
func validatePCRAttDoc(attDocRaw []byte) (map[uint32][]byte, error) {
|
||||
attDoc := vtpm.AttestationDocument{}
|
||||
if err := json.Unmarshal(attDocRaw, &attDoc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if attDoc.Attestation == nil {
|
||||
return nil, errors.New("empty attestation")
|
||||
}
|
||||
qIdx, err := vtpm.GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx, pcr := range attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs {
|
||||
if len(pcr) != 32 {
|
||||
return nil, fmt.Errorf("incomplete PCR at index: %d", idx)
|
||||
}
|
||||
}
|
||||
return attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, nil
|
||||
}
|
||||
|
||||
// printPCRs formates and prints PCRs to the given writer.
|
||||
func printPCRs(w io.Writer, pcrs map[uint32][]byte) error {
|
||||
pcrJSON, err := json.MarshalIndent(pcrs, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "PCRs:\n%s\n", string(pcrJSON))
|
||||
return nil
|
||||
}
|
||||
|
||||
// exportToFile writes pcrs to a file, formatted to be valid Go code.
|
||||
// Validity of the PCR map is not checked, and should be handled by the caller.
|
||||
func exportToFile(path string, pcrs map[uint32][]byte, fs *afero.Afero) error {
|
||||
goCode := `package pcrs
|
||||
|
||||
var pcrs = map[uint32][]byte{%s
|
||||
}
|
||||
`
|
||||
pcrsFormatted := ""
|
||||
for i := 0; i < len(pcrs); i++ {
|
||||
pcrHex := fmt.Sprintf("%#02X", pcrs[uint32(i)][0])
|
||||
for j := 1; j < len(pcrs[uint32(i)]); j++ {
|
||||
pcrHex = fmt.Sprintf("%s, %#02X", pcrHex, pcrs[uint32(i)][j])
|
||||
}
|
||||
|
||||
pcrsFormatted = pcrsFormatted + fmt.Sprintf("\n\t%d: {%s},", i, pcrHex)
|
||||
}
|
||||
|
||||
return fs.WriteFile(path, []byte(fmt.Sprintf(goCode, pcrsFormatted)), 0o644)
|
||||
}
|
246
hack/pcr-reader/main_test.go
Normal file
246
hack/pcr-reader/main_test.go
Normal file
|
@ -0,0 +1,246 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/coordinator/oid"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/proto/tpm"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetVerifyPeerCertificateFunc(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
rawCerts [][]byte
|
||||
wantErr bool
|
||||
}{
|
||||
"no certificates": {
|
||||
rawCerts: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid certificate": {
|
||||
rawCerts: [][]byte{
|
||||
{0x1, 0x2, 0x3},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no extension": {
|
||||
rawCerts: [][]byte{
|
||||
mustGenerateTestCert(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(123),
|
||||
}),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"certificate with attestation": {
|
||||
rawCerts: [][]byte{
|
||||
mustGenerateTestCert(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(123),
|
||||
ExtraExtensions: []pkix.Extension{
|
||||
{
|
||||
Id: oid.GCP{}.OID(),
|
||||
Value: []byte{0x1, 0x2, 0x3},
|
||||
Critical: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
attDoc := &[]byte{}
|
||||
verify := getVerifyPeerCertificateFunc(attDoc)
|
||||
|
||||
err := verify(tc.rawCerts, nil)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
|
||||
assert.NotNil(attDoc)
|
||||
cert, err := x509.ParseCertificate(tc.rawCerts[0])
|
||||
require.NoError(err)
|
||||
assert.Equal(cert.Extensions[0].Value, *attDoc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustGenerateTestCert(t *testing.T, template *x509.Certificate) []byte {
|
||||
require := require.New(t)
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(err)
|
||||
cert, err := x509.CreateCertificate(rand.Reader, template, template, priv.Public(), priv)
|
||||
require.NoError(err)
|
||||
return cert
|
||||
}
|
||||
|
||||
func TestExportToFile(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
pcrs map[uint32][]byte
|
||||
fs *afero.Afero
|
||||
wantErr bool
|
||||
}{
|
||||
"file not writeable": {
|
||||
pcrs: map[uint32][]byte{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
fs: &afero.Afero{Fs: afero.NewReadOnlyFs(afero.NewMemMapFs())},
|
||||
wantErr: true,
|
||||
},
|
||||
"file writeable": {
|
||||
pcrs: map[uint32][]byte{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
fs: &afero.Afero{Fs: afero.NewMemMapFs()},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
path := "test-file"
|
||||
err := exportToFile(path, tc.pcrs, tc.fs)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
content, err := tc.fs.ReadFile(path)
|
||||
require.NoError(err)
|
||||
|
||||
for _, pcr := range tc.pcrs {
|
||||
for _, register := range pcr {
|
||||
assert.Contains(string(content), fmt.Sprintf("%#02X", register))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePCRAttDoc(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
attDocRaw []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"invalid attestation document": {
|
||||
attDocRaw: []byte{0x1, 0x2, 0x3},
|
||||
wantErr: true,
|
||||
},
|
||||
"nil attestation": {
|
||||
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{}),
|
||||
wantErr: true,
|
||||
},
|
||||
"nil quotes": {
|
||||
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||
Attestation: &attest.Attestation{},
|
||||
}),
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid PCRs": {
|
||||
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||
Attestation: &attest.Attestation{
|
||||
Quotes: []*tpm.Quote{
|
||||
{
|
||||
Pcrs: &tpm.PCRs{
|
||||
Hash: tpm.HashAlgo_SHA256,
|
||||
Pcrs: map[uint32][]byte{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
wantErr: true,
|
||||
},
|
||||
"valid PCRs": {
|
||||
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||
Attestation: &attest.Attestation{
|
||||
Quotes: []*tpm.Quote{
|
||||
{
|
||||
Pcrs: &tpm.PCRs{
|
||||
Hash: tpm.HashAlgo_SHA256,
|
||||
Pcrs: map[uint32][]byte{
|
||||
0: []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
pcrs, err := validatePCRAttDoc(tc.attDocRaw)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
|
||||
attDoc := vtpm.AttestationDocument{}
|
||||
require.NoError(json.Unmarshal(tc.attDocRaw, &attDoc))
|
||||
qIdx, err := vtpm.GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
|
||||
require.NoError(err)
|
||||
assert.EqualValues(attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, pcrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshalAttDoc(t *testing.T, attDoc vtpm.AttestationDocument) []byte {
|
||||
attDocRaw, err := json.Marshal(attDoc)
|
||||
require.NoError(t, err)
|
||||
return attDocRaw
|
||||
}
|
||||
|
||||
func TestPrintPCRs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pcrs := map[uint32][]byte{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
err := printPCRs(&out, pcrs)
|
||||
assert.NoError(err)
|
||||
|
||||
for idx, pcr := range pcrs {
|
||||
assert.Contains(out.String(), fmt.Sprintf("\"%d\": ", idx))
|
||||
assert.Contains(out.String(), fmt.Sprintf(": \"%s\"", base64.StdEncoding.EncodeToString(pcr)))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue