-
Notifications
You must be signed in to change notification settings - Fork 8
Accept dstack-mr-gcp measurements format #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
843bb54
462c957
ca18021
7f65ddd
59917c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,12 +8,14 @@ package multimeasurements | |
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "os" | ||
| "strings" | ||
|
|
||
| "github.com/flashbots/cvm-reverse-proxy/internal/attestation/measurements" | ||
| "github.com/flashbots/cvm-reverse-proxy/internal/encoding" | ||
| ) | ||
|
|
||
| // MultiMeasurements holds several known measurements, and can check if | ||
|
|
@@ -30,35 +32,51 @@ type MeasurementsContainer struct { | |
|
|
||
| type LegacyMultiMeasurements map[string]measurements.M | ||
|
|
||
| // Caps expansion of dstack-mr-gcp measurements | ||
| const maxGCPMeasurementContainers = 10_000 | ||
|
|
||
| // Structure used by the dstack-mr-gcp output. mrtd and rtmr0 hold one entry per | ||
| // possible value; rtmr1-3 are single values. | ||
| type rawGCPMeasurements struct { | ||
| MRTD []encoding.HexBytes `json:"mrtd"` | ||
| RTMR0 []encoding.HexBytes `json:"rtmr0"` | ||
| RTMR1 encoding.HexBytes `json:"rtmr1"` | ||
| RTMR2 encoding.HexBytes `json:"rtmr2"` | ||
| RTMR3 encoding.HexBytes `json:"rtmr3"` | ||
| } | ||
|
|
||
| // New returns a MultiMeasurements instance, with the measurements | ||
| // loaded from a file or URL. | ||
| func New(path string) (m *MultiMeasurements, err error) { | ||
| var data []byte | ||
| func New(path string) (*MultiMeasurements, error) { | ||
| if strings.HasPrefix(path, "http") { | ||
| // load from URL | ||
| resp, err := http.Get(path) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer resp.Body.Close() | ||
| data, err = io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } else { | ||
| // load from file | ||
| data, err = os.ReadFile(path) | ||
| data, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return NewFromBytes(data) | ||
| } | ||
| data, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return NewFromBytes(data) | ||
| } | ||
|
|
||
| m = &MultiMeasurements{} | ||
| // NewFromBytes returns a MultiMeasurements instance loaded from JSON bytes | ||
| func NewFromBytes(data []byte) (*MultiMeasurements, error) { | ||
| m := &MultiMeasurements{} | ||
|
|
||
| // Try to load the v2 data schema, if that fails fall back to legacy v1 schema | ||
| if err = json.Unmarshal(data, &m.Measurements); err != nil { | ||
| var legacyData LegacyMultiMeasurements | ||
| err = json.Unmarshal(data, &legacyData) | ||
| if err := json.Unmarshal(data, &m.Measurements); err == nil { | ||
| return m, nil | ||
| } | ||
|
|
||
| var legacyData LegacyMultiMeasurements | ||
| if err := json.Unmarshal(data, &legacyData); err == nil { | ||
| for measurementID, measurements := range legacyData { | ||
| container := MeasurementsContainer{ | ||
| MeasurementID: measurementID, | ||
|
|
@@ -67,9 +85,78 @@ func New(path string) (m *MultiMeasurements, err error) { | |
| } | ||
| m.Measurements = append(m.Measurements, container) | ||
| } | ||
| return m, nil | ||
| } | ||
|
|
||
| rawGCPContainers, err := parseRawGCPMeasurements(data) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| m.Measurements = rawGCPContainers | ||
| return m, nil | ||
| } | ||
|
|
||
| // parseRawGCPMeasurements expands dstack-mr-gcp JSON into DCAP TDX measurement containers | ||
| func parseRawGCPMeasurements(data []byte) ([]MeasurementsContainer, error) { | ||
| var raw rawGCPMeasurements | ||
| if err := json.Unmarshal(data, &raw); err != nil { | ||
| return nil, fmt.Errorf("parsing raw GCP measurements: %w", err) | ||
| } | ||
|
|
||
| if raw.RTMR3 == nil { | ||
| raw.RTMR3 = make(encoding.HexBytes, measurements.TDXMeasurementLength) | ||
| } | ||
| if err := validateGCPMeasurements(raw); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| total := len(raw.MRTD) * len(raw.RTMR0) | ||
| if total > maxGCPMeasurementContainers { | ||
| return nil, fmt.Errorf("parsing raw GCP measurements: cartesian product of %d containers exceeds limit of %d", total, maxGCPMeasurementContainers) | ||
| } | ||
|
|
||
| containers := make([]MeasurementsContainer, 0, total) | ||
| for mrtdIdx, mrtd := range raw.MRTD { | ||
| for rtmr0Idx, rtmr0 := range raw.RTMR0 { | ||
| containers = append(containers, MeasurementsContainer{ | ||
| MeasurementID: fmt.Sprintf("dstack-mr-gcp-%d-%d", mrtdIdx, rtmr0Idx), | ||
| AttestationType: "dcap-tdx", | ||
| Measurements: measurements.M{ | ||
| 0: {Expected: mrtd, ValidationOpt: measurements.Enforce}, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine to not support optional measurements for dstack-mr format, for two reasons:
If someone needs to ignore a specific measurement register, there's no good way of doing it in dstack-mr format and I'd argue it's a better idea to not allow ignoring measurements in this format. |
||
| 1: {Expected: rtmr0, ValidationOpt: measurements.Enforce}, | ||
| 2: {Expected: raw.RTMR1, ValidationOpt: measurements.Enforce}, | ||
| 3: {Expected: raw.RTMR2, ValidationOpt: measurements.Enforce}, | ||
| 4: {Expected: raw.RTMR3, ValidationOpt: measurements.Enforce}, | ||
| }, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return containers, nil | ||
| } | ||
|
|
||
| // validateGCPMeasurements checks that all dstack-mr-gcp fields are present and have valid TDX measurement lengths | ||
| func validateGCPMeasurements(raw rawGCPMeasurements) error { | ||
| lists := map[string][]encoding.HexBytes{"mrtd": raw.MRTD, "rtmr0": raw.RTMR0} | ||
| for field, values := range lists { | ||
| if len(values) == 0 { | ||
| return fmt.Errorf("parsing raw GCP measurements: %q must not be empty", field) | ||
| } | ||
| for idx, value := range values { | ||
| if len(value) != measurements.TDXMeasurementLength { | ||
| return fmt.Errorf("parsing raw GCP measurements: %q[%d] has invalid length %d", field, idx, len(value)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| scalars := map[string]encoding.HexBytes{"rtmr1": raw.RTMR1, "rtmr2": raw.RTMR2, "rtmr3": raw.RTMR3} | ||
| for field, value := range scalars { | ||
| if len(value) != measurements.TDXMeasurementLength { | ||
| return fmt.Errorf("parsing raw GCP measurements: %q has invalid length %d", field, len(value)) | ||
| } | ||
| } | ||
|
|
||
| return m, err | ||
| return nil | ||
| } | ||
|
|
||
| // Contains checks if the provided measurements match one of the known measurements. Any keys in the provided | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m *MultiMeasurementsin return is never set, the old constructor worked a bit differently. I'd suggest to either keep to a single function, or remove the named return (making NewFromBytes a member function is an option but could be weird).