DCAP attestation: Add GCP provenence check to establish whether the associated PPID is endorsed by Google - #54
DCAP attestation: Add GCP provenence check to establish whether the associated PPID is endorsed by Google#54ameba23 wants to merge 23 commits into
Conversation
* main: Bump reqwest to 0.13.4 chore(deps): bump openssl from 0.10.79 to 0.10.80
| expected_input_data: [u8; 64], | ||
| pccs: Option<Pccs>, | ||
| ) -> Result<MultiMeasurements, DcapVerificationError> { | ||
| ) -> Result<(MultiMeasurements, Quote), DcapVerificationError> { |
There was a problem hiding this comment.
To avoid parsing the quote a second time to extract the PPID after verification, the verifier function now returns the parsed quote.
| @@ -0,0 +1,70 @@ | |||
| //! On GCP check MRTD values map to Google endorsed firmware | |||
There was a problem hiding this comment.
This file is mostly unchanged from main - i just refactored it into a separate file to avoid having both provenance and firmware stuff together in one file.
| /// OS image | ||
| pub measurement_id: String, | ||
| /// The attestation type this record accepts | ||
| pub attestation_type: AttestationType, |
There was a problem hiding this comment.
To enforce the GCP provenance check, attestation policies need to explictly state the expected attestation type
| const GCP_PROVENANCE_REGISTRY_URL: &str = | ||
| "https://storage.googleapis.com/confidential-host-registry"; |
There was a problem hiding this comment.
should this be always fixed or should we also make it optionally set by an env var and if not provided it defaults to this URL instead?
This could also serve for future path in case PPID is deprecated or if the verifier would like to pull the registry from a different source.
| { | ||
| let known_gcp_ppids = self | ||
| .known_gcp_ppids | ||
| .read() | ||
| .map_err(|err| GcpProvenanceError::CacheLock(err.to_string()))?; | ||
| if let Some(stored_at) = known_gcp_ppids.get(&ppid) && | ||
| is_cache_entry_fresh(*stored_at, now) | ||
| { | ||
| return Ok(()); | ||
| } | ||
| } | ||
|
|
||
| // Re-check under the write lock in case another thread refreshed the | ||
| // entry while we were waiting, and drop stale entries so we refetch. | ||
| { | ||
| let mut known_gcp_ppids = self | ||
| .known_gcp_ppids | ||
| .write() | ||
| .map_err(|err| GcpProvenanceError::CacheLock(err.to_string()))?; | ||
| if let Some(stored_at) = known_gcp_ppids.get(&ppid) { | ||
| if is_cache_entry_fresh(*stored_at, now) { | ||
| return Ok(()); | ||
| } | ||
| known_gcp_ppids.remove(&ppid); | ||
| } | ||
| } |
There was a problem hiding this comment.
This seems redundant and can be simplified on its own
There was a problem hiding this comment.
yeah i think you are right, we don't need the extra check
There was a problem hiding this comment.
i just tried to replace this with this, which is much simpler:
{
let known_gcp_ppids = self
.known_gcp_ppids
.read()
.map_err(|err| GcpProvenanceError::CacheLock(err.to_string()))?;
if known_gcp_ppids
.get(&ppid)
.is_some_and(|stored_at| is_cache_entry_fresh(*stored_at, now))
{
return Ok(());
}
}
This works, but we lose the code path which removes stale entries. Which gives correct behavior but means the cache can get bigger and bigger over time.
| #[error("blocking task join: {0}")] | ||
| TaskJoin(String), |
There was a problem hiding this comment.
Nit: semantically doesn't seem to be a GcpProvenanceError type but rather a threading or async kind of error type, or did I misunderstand something here ?
There was a problem hiding this comment.
No, semantically its not. This would happen if the task handling the provenance check panics. Which if this is implemented correctly should be highly unlikely to happen but we have to handle this case anyway. I would argue it make sense to categorize it as this because it is a possible bad outcome of running the provenance check.
|
Small suggestion: |
If i understand you right, you mean we should handle the case that the verifier doesn't care whether or not its a GCP attestation - any DCAP attestation will do. Currently you can get this by specifying 'DcapTdx' as the attestation type in the policy. However, if the server claims their attestation type is I see the logic here, but i'd be wary of skipping the check in that case, because if the server indicates that they are running on GCP but is not, i don't think we should treat that as a valid attestation. In the happy path, the non-GCP server correctly submits platform metadata as If your concern is more just that we are starting to bloat the whole process with GCP-specific logic, then yes i can see an argument for putting it behind a feature flag and not compiling it on builds where we don't care about GCP. |
When verifying an attestation which claims to be of type
AttestationType::GcpTdxthis PR adds an additional check as to whether the PPID from the PCK certificate included in the attestation is present in GCP's public bucket, which indicates that the PPID belongs to them. This is essentially a 'proof-of-cloud' check specially for GCP.This is based on Google's own provenance checker tool written in Go: https://github.com/google/go-tdx-guest/blob/main/tools/gceprovenance/main.go
In order to match the Go implementation we:
Unlike the Go implementation we additionally:
zoneandtimestamp.Note: The Go implementation offers an additional instance-verification check as well as the provenance check. This checks the MR_OWNER value against instance metadata. This is outside of the scope of this PR and not implemented.
Note: No checks are made on the timestamp or zone details in the response, other than making sure those fields are present. This is because the Go implementation also does not check those values. Such checks could be added later in a follow-up.
See relevant documentation from Google:
TODO: