mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-28 17:09:30 -05:00
d52f3db2a3
* Fetch measurements from CDN * Perform metadata validation on fetched measurements * Remove deprecated public bucket Signed-off-by: Daniel Weiße <dw@edgeless.systems>
203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
|
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
|
"github.com/edgelesssys/constellation/v2/verify/verifyproto"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func main() {
|
|
coordIP := flag.String("constell-ip", "", "Public IP of the Constellation")
|
|
port := flag.String("constell-port", strconv.Itoa(constants.VerifyServiceNodePortGRPC), "NodePort of the Constellation's verification service")
|
|
format := flag.String("format", "json", "Output format: json, yaml (default json)")
|
|
quiet := flag.Bool("q", false, "Set to disable output")
|
|
timeout := flag.Duration("timeout", 2*time.Minute, "Wait this duration for the verification service to become available")
|
|
metadata := flag.Bool("metadata", false, "Include image metadata (CSP, image UID) for publishing")
|
|
csp := flag.String("csp", "", "Define CSP for metadata")
|
|
image := flag.String("image", "", "Define image UID for metadata from which image the PCRs are taken from")
|
|
|
|
flag.Parse()
|
|
|
|
if *coordIP == "" || *port == "" {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if *metadata && (*csp == "" || *image == "") {
|
|
fmt.Println("If you enable metadata, you also need to define a CSP and an image to include from as arguments.")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
addr := net.JoinHostPort(*coordIP, *port)
|
|
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
|
defer cancel()
|
|
|
|
attDocRaw, err := getAttestation(ctx, addr)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
pcrs, err := validatePCRAttDoc(attDocRaw)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if *quiet {
|
|
return
|
|
}
|
|
|
|
if *metadata {
|
|
outputWithMetadata := measurements.WithMetadata{
|
|
CSP: cloudprovider.FromString(*csp),
|
|
Image: strings.ToLower(*image),
|
|
Measurements: pcrs,
|
|
}
|
|
err = printPCRsWithMetadata(os.Stdout, outputWithMetadata, *format)
|
|
} else {
|
|
err = printPCRs(os.Stdout, pcrs, *format)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// getAttestation connects to the Constellation verification service and returns its attestation document.
|
|
func getAttestation(ctx context.Context, addr string) ([]byte, error) {
|
|
conn, err := grpc.DialContext(
|
|
ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to connect to verification service: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
nonce, err := crypto.GenerateRandomBytes(32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := verifyproto.NewAPIClient(conn)
|
|
res, err := client.GetAttestation(ctx, &verifyproto.GetAttestationRequest{Nonce: nonce, UserData: nonce})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.Attestation, nil
|
|
}
|
|
|
|
// validatePCRAttDoc parses and validates PCRs of an attestation document.
|
|
func validatePCRAttDoc(attDocRaw []byte) (measurements.M, 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
|
|
}
|
|
|
|
m := measurements.M{}
|
|
for idx, pcr := range attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs {
|
|
if len(pcr) != 32 {
|
|
return nil, fmt.Errorf("incomplete PCR at index: %d", idx)
|
|
}
|
|
|
|
m[idx] = measurements.Measurement{
|
|
Expected: *(*[32]byte)(pcr),
|
|
WarnOnly: true,
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// printPCRs formats and prints PCRs to the given writer.
|
|
// format can be one of 'json' or 'yaml'. If it doesn't match defaults to 'json'.
|
|
func printPCRs(w io.Writer, pcrs measurements.M, format string) error {
|
|
switch format {
|
|
case "json":
|
|
return printPCRsJSON(w, pcrs)
|
|
case "yaml":
|
|
return printPCRsYAML(w, pcrs)
|
|
default:
|
|
return printPCRsJSON(w, pcrs)
|
|
}
|
|
}
|
|
|
|
// printPCRs formats and prints PCRs to the given writer.
|
|
// format can be one of 'json' or 'yaml'. If it doesn't match defaults to 'json'.
|
|
func printPCRsWithMetadata(w io.Writer, outputWithMetadata measurements.WithMetadata, format string) error {
|
|
switch format {
|
|
case "json":
|
|
return printPCRsJSONWithMetadata(w, outputWithMetadata)
|
|
case "yaml":
|
|
return printPCRsYAMLWithMetadata(w, outputWithMetadata)
|
|
default:
|
|
return printPCRsJSONWithMetadata(w, outputWithMetadata)
|
|
}
|
|
}
|
|
|
|
func printPCRsYAML(w io.Writer, pcrs measurements.M) error {
|
|
pcrYAML, err := yaml.Marshal(pcrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "%s", string(pcrYAML))
|
|
return nil
|
|
}
|
|
|
|
func printPCRsYAMLWithMetadata(w io.Writer, outputWithMetadata measurements.WithMetadata) error {
|
|
pcrYAML, err := yaml.Marshal(outputWithMetadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "%s", string(pcrYAML))
|
|
return nil
|
|
}
|
|
|
|
func printPCRsJSON(w io.Writer, pcrs measurements.M) error {
|
|
pcrJSON, err := json.MarshalIndent(pcrs, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "%s", string(pcrJSON))
|
|
return nil
|
|
}
|
|
|
|
func printPCRsJSONWithMetadata(w io.Writer, outputWithMetadata measurements.WithMetadata) error {
|
|
pcrJSON, err := json.MarshalIndent(outputWithMetadata, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(w, "%s", string(pcrJSON))
|
|
return nil
|
|
}
|