aws / aws-nitro-enclaves-nsm-api

This provides a library for interacting with the Nitro Secure Module, which provides Nitro Enclaves with attestation capability.
Apache License 2.0
62 stars 43 forks source link

Request attestation document inside vsock-sample.py #9

Closed Jonas-Metzger closed 3 years ago

Jonas-Metzger commented 3 years ago

Hi @petreeftime and other contributors!

You have this neat sample showing how to communicate with the enclave ("server") from the EC2 instance ("client"), in python:

https://github.com/aws/aws-nitro-enclaves-samples/blob/main/vsock_sample/py/vsock-sample.py

I'm wondering how to get the enclave/server side of the code to request the attestation document (to then share it with the client). Is there a CLI command that would obtain it from the Nitro Hypervisor? Otherwise, I assume I'd have to use a python-rust bindings utility like pyo3. Which is the right rust function to point it to?

Am I correct in assuming that I will be able to include arbitrary data (in particular: a public key manually generated by the python code running inside the enclave) into the attestation document by including it in the attestation request call?

Thanks a lot!

petreeftime commented 3 years ago

Also answering for: https://github.com/aws/aws-nitro-enclaves-sdk-c/issues/57.

We don't currently have a python API for this, specifically, although pyo3 could be a neat solution to it, but we haven't looked into it so far.

In broad, to get an Attestation Document containing any form of user data, the user needs to create a request for it:

fn main() {
    let nsm_fd = nsm_driver::nsm_init();

    let public_key = serde_bytes::ByteBuf::from("my super secret key");
    let hello = serde_bytes::ByteBuf::from("hello, world!");

    let request = nsm_io::Request::Attestation {
        public_key: Some(public_key),
        user_data: Some(hello),
        nonce: None,
    };

    let response = nsm_driver::nsm_process_request(nsm_fd, request);
    println!("{:?}", response);

    nsm_driver::nsm_exit(nsm_fd);
}

Another solution for python, but less elegant, would be create an executable that python can call using the already available libraries.

Jonas-Metzger commented 3 years ago

Thanks a lot @petreeftime ! Sorry for spamming, I wasn't sure about the right place to ask.

I'm not familiar with Rust at all, so I'll try to code up some way to call your fn main() from python. I hope you'll bear with me while I figure out what to do.

Compared to pyo3, it actually seems easier for me to just code a minimal working attest.rs containing your fn main() and use runner to run it with a simple shell command (e.g. from python's subprocess). The readme at

https://github.com/aws/aws-nitro-enclaves-samples/tree/main/vsock_sample/py

describes a specific container setup process for the enclave that suffices to run the vsock example. Let's say attest.rs is inside that container. Are there any use statements I need to put inside attest.rs before fn main()? Do I need to explicitly install aws-nitro-enclaves-nsm-api somehow, or maybe nsm_driver serde_bytes and nsm_io manually? Or does Nitro make sure that these tools are all installed inside every .eif automatically?

If you have the time, it would be super helpful if you could also tell me how the fn main() snippet has to change to make it

petreeftime commented 3 years ago

The enclave itself doesn't provide any software, and all the software comes from the person building the enclave, via the build-enclave command. The default tooling provides a Linux kernel, and init process + parameters for it and whatever the user specifies as the Docker image to use, so in this case all the user software comes from that Docker image.

With regards to how one would build the Rust code, I'll look to adding a sample to the samples repo, but in short: you first need to declare the dependency in the Cargo.toml file of the project:

[dependencies]
serde_bytes = "0.11"

[dependencies.nsm-driver]
git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git"
rev = "4f468c4"

[dependencies.nsm-io]
git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git"
rev = "4f468c4"

The use inside the files just helps with keeping the lines short, and doesn't have an impact on usage otherwise. For example, the code above can be "compressed" to:

use nsm_io::Request;
use serde_bytes::ByteBuf;

fn main() {
    let nsm_fd = nsm_driver::nsm_init();

    let public_key = ByteBuf::from("my super secret key");
    let hello = ByteBuf::from("hello, world!");

    let request = Request::Attestation {
        public_key: Some(public_key),
        user_data: Some(hello),
        nonce: None,
    };

    let response = nsm_driver::nsm_process_request(nsm_fd, request);
    println!("{:?}", response);

    nsm_driver::nsm_exit(nsm_fd);
}

The other important aspect here is the docker file. I will come back with an example that builds both a Rust binary and includes it in a Docker container container with Python.

Jonas-Metzger commented 3 years ago

Thank you very much for the clarification and the little Rust 101 @petreeftime ! I think I'm beginning to understand what's going on. A minimal docker example that builds such a Rust binary and contains client-server communication in python similar to the vsock-sample would be awesome. Should then be straightforward to call this binary from python's subprocess (no pyo3 or runner needed), right?

Thank you so much for taking the time to do this! That will be way more efficient than me trying to get these basics to work via trial and error. Keep me posted!

Jonas-Metzger commented 3 years ago

Hi @petreeftime , I'd love to start building off the docker container example you offered to share. What's your guess for when you'll be able to share it?

I'm having trouble understanding how I would have to set up the container to get it to build a working rust executable inside the .eif

dblanzeanu commented 3 years ago

Hi @Jonas-Metzger, please check the open PR which implements what @petreeftime suggested above. The PR is still work in progress, however this should get you started.

Jonas-Metzger commented 3 years ago

Thank you so much for your efforts! I'll try it out asap!

Jonas-Metzger commented 3 years ago

Thank you again @dblanzeanu and @petreeftime , the att-doc-retriever-sample works for me now, and I was able to play around and modify the python and rust code a bit, allowing python to insert custom values into the public_key and user_data fields of the attestation request. I was also able to decode the resulting attestation document using cbor2.loads(bytes(integer_list)), where integer_list are the bytes of the attestation document, which I currently manually collect by copying from the console output.

In @dblanzeanu 's example, what's the best way to collect the vsock data stream into a single string in python, and close the connection after all the transmission is completed?

Thanks a lot for your support!

Jonas-Metzger commented 3 years ago

Found a solution here: https://stackoverflow.com/a/17668009.

Everything works now. Thanks!