On 3/3/22 12:40 PM, Daniel P. Berrangé wrote:
On Fri, Feb 25, 2022 at 04:11:27PM -0500, Tobin Feldman-Fitzthum
wrote:
> Some comments on the example protocol stuff
>
> On 2/23/22 1:38 PM, Dov Murik wrote:
>> +cc Tobin, James
>>
>> On 23/02/2022 19:28, Daniel P. Berrangé wrote:
>>>
>>>
>>> What could this look like from POV of an attestation server API, if
>>> we assume HTTPS REST service with a simple JSON payload .>>
>>>
>>> * Guest Owner: Register a new VM to be booted:
> We're trying to set the API between libvirt and the AS. I would assume
> that the API between the AS and the guest owner is out of scope,
> although maybe this is just an example.
Agreed, it is out of scope from libvirt's POV. I just wanted to
illustrate a possible end-to-end solution for all parties.
>>>
>>> POST /vm/<UUID>
> Note that this is a privileged endpoint (unlike the ones below).
>>>
>>> Request body:
>>>
>>> {
>>> "scheme": "amd-sev",
>>> "cloud-cert": "certificate of the cloud owner that
signs the PEK",
>>> "policy": 0x3,
>>> "cpu-count": 3,
>>> "firmware-hashes": [
>>> "xxxx",
>>> "yyyy",
>>> ],
> I think we'd need to provide the full firmware binary rather than just
> the hash if we plan to calculate the launch digest in the AS.
> Alternatively the guest owner can calculate the launch digest themself
> and pass it to the AS. This is what kbs-rs does. There are pros and cons
> to both and we should probably support both (which should be easy).
Since this particular endpoint is an interface exclusively between
the guest owner and the AS, it could be said to be an API that does
not need standardization. Different implementations may choose to
approach it different ways based on how they evaluate the tradeoffs.
>>> "kernel-hash": "aaaa",
>>> "initrd-hash": "bbbb",
>>> "cmdline-hash": "cccc",
>>> "secrets": [
>>> {
>>> "type": "luks-passphrase",
>>> "passphrase": "<blah>"
>>> }
>>> ]
>>> }
>>>
>
> Registering an individual VM is kind of an interesting perspective. With
> kbs-rs, rather than registering an individual VM, the guest owner
> registers secrets and can set a policy (which specifies launch
> parameters like the SEV policy) for each secret. Then secrets are
> released to VMs that meet the policy requirements. There isn't really
> any tracking of an individual VM (besides the secure channel briefly
> used for secret injection). In SEV(-ES) individual VMs don't really have
> an identity separate from their launch parameters and launch
> measurement. I guess we're not trying to design an AS here, so we can
> leave for another time.
Agree with what you say here.
The distinction of registering a single VM vs registering an image
that can be instantiated to many VMs can likely be a decision for
the specific implementation of the AS.
The reason I suggested registering an individual VM was that I was
trying to more closely match the behaviour the virt platform would
have if it was not talkin directly with an attestation service.
In the manul case a guest owner feeds in the launch blob for each
VM at boot time. Thus the compute host can't boot instances of the
VM without explicit action from the user. If the AS releases the
launch blob and secrets upon request from teh compute host, it can
potentially boot many instances of a VM even if the guest owner
only asked for one.
Of course the host admin can't get into the VMs todo anything, but
the mere act of being able to launch many instances without guest
owner action might lead to a denial of service attack on other
things that the VM talks to.
None the less this risk is easy to mitigate, even if you're just
registering an image with the AS. It could easily be set to
require a confirmation befere releasing more than 'n' instances
of the launch blob and secrets
There are some very interesting questions on the borders of
confidentiality and orchestration. In Confidential Containers we try to
separate those things as much as possible. Confidential Computing can
guarantee confidentiality, but is it the right technology for preventing
DoS or resource starvation? If the underlying hardware doesn't provide
guarantees about host behavior, why would we have any guarantees about
the behavior of an orchestrator. On the other hand, you point out that
it's actually easy to enforce certain guarantees via key release. There
are other things we can do to regulate orchestration, but they usually
involve the attestation server knowing more and more about the cloud
environment. Fortunately we aren't designing an attestation server here,
so we can skip these questions, but I think they're really interesting.
>>>
>>>
>>> * Libvirt: Request permission to launch a VM on a host
>>>
>>> POST /vm/<UUID>/launch
> Since I've been thinking about VM identity a little differently, our
> setup for the UUID is a bit different as well. We use a UUID to track a
> connection (as in TIK, TEK), but this is not known at the time of the
> launch request (aka GetBundle request). Instead, the UUID is returned in
> the launch response so that it can be used for the secret request.
>
> If we have a UUID in the launch request, it creates an interesting
> coordination requirement between the CSP and the AS. Imagine a situation
> where we spin up a bunch of identical VMs dynamically. Here the guest
> owner would have to register a new VM with a UUID for each instance and
> then get all of that information over to libvirt. This seems difficult.
> Shifting focus from VMs to secrets and policies and automatically
> provisioning the UUID sidesteps this issue. This is especially important
> for something like Confidential Containers (of course CC doesn't use
> libvirt but we'd like to share the AS API).
From the libvirt POV, we don't have to declare what the UUID
represents. It could represent a single VM instances, or it could
represent a VM image supporting many instances. Libvirt wouldn't
care, the UUID is just a token passed to the AS to indicate what
libvirt needs to launch.
I think you need to uniquely identify the connection between the PSP and
the AS so that you use the same TIK/TEK to wrap the keys as was provided
on startup. We don't want to reuse these. In other words the UUID should
be unique for every startup.
>>>
>>> Request body:
>>>
>>> {
>>> "pdh": "<blah>",
>>> "cert-chain": "<blah>",
>>> "cpu-id": "<CPU ID>",
> There's an interesting question as to whether the CEK should be signed
> by libvirt or by the AS.
I'm mostly ambivalent on that question - either way works well
enough, though if libvirt needs todo the signing, then libvirt
needs to be able to talk to AMD's REST service to acquire the
signature. If libvirt doesn't have network access from the
compute host, it might not be possible for it to acquire the
signature.
In terms of the protocol spec, both approaches could be supported.
The 'cpu-id' can be provided unconditionally. The 'cert-chain' can
be declared to be signed or unsigned. If libvirt is capable of
getting a signature, it could provide the cert-chain with thue CEK
signed. If not, then the AS could acquire the signature itself as
a fallback.
>>> ...other relevant bits...
>>> }
>>>
>>> Service decides if the proposed host is acceptable
>>>
>>> Response body (on success)
>>>
>>> {
>>> "session": "<blah>",
>>> "owner-cert": "<blah>",
>>> "policy": 3,
> I've assumed that the policy would be part of the request, having been
> set in the libvirt XML.
My thought was that since the guest owner needs to explicitly give
the AS the policy in order to create the correct launch blob, it
is pointless for the guest owner to give it to libvirt again in
the XML. Just give it the AS and let it pass it onto libvirt
automatically. Again though, both approaches work - even if libvirt
alrady has the policy, there's no harm in the AS providing the
policy in its response again.
Yeah, not a big deal either way.
>>> * Libvirt: Request secrets to inject to launched VM
>>>
>>> POST /vm/<UUID>/validate
>>>
>>> Request body:
>>>
>>> {
>>> "api-minor": 1,
>>> "api-major": 2,
>>> "build-id": 241,
>>> "policy": 3,
>>> "measurement": "<blah>",
>>> "firmware-hash": "xxxx",
>>> "cpu-count": 3,
>>> ....other relevant stuff....
>>> }
>>>
>>> Service validates the measurement...
>>>
>>> Response body (on success):
>>>
>>> {
>>> "secret-header": "<blah>",
>>> "secret-table": "<blah>",
> Referring to secret payload format as OVMF secret table?
Essentially I intended it to be the data that is expected by
the 'sev-inject-launch-secret' QMP command from QEMU, which
is consumed by libvirt. If using the OVMF firmware with the
guest then its the OVMF secret table, with other firmware it
is whatever they declare it to be. Calling it 'secret-payload'
probably makes more sense.
Ok
-Tobin
Regards,
Daniel