From ef40ae6ad3b099fa6e0daa3126d6aaad043c5a10 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 1 Jul 2024 09:50:20 +0100 Subject: [PATCH] Reproducible builds --- .github/workflows/glove-build-env.yml | 53 +++++++++++++++++++++++++++ .github/workflows/rust.yml | 22 ----------- Cargo.lock | 10 +++++ README.md | 27 ++++++-------- build-service-enclave.sh | 8 ---- build.sh | 24 ++++++++++++ common/Cargo.toml | 2 +- enclave/Dockerfile | 9 ++--- enclave/src/main.rs | 4 +- glove-build-env/Dockerfile | 15 ++++++++ service/src/enclave.rs | 2 +- service/src/main.rs | 13 +++++-- 12 files changed, 131 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/glove-build-env.yml delete mode 100644 .github/workflows/rust.yml delete mode 100755 build-service-enclave.sh create mode 100755 build.sh create mode 100644 glove-build-env/Dockerfile diff --git a/.github/workflows/glove-build-env.yml b/.github/workflows/glove-build-env.yml new file mode 100644 index 0000000..09265ea --- /dev/null +++ b/.github/workflows/glove-build-env.yml @@ -0,0 +1,53 @@ +name: Create and publish glove-build-env image + +#on: workflow_dispatch + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/glove-build-env + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # https://github.com/docker/metadata-action + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: type=sha,format=long + + # https://github.com/docker/build-push-action + - name: Build and push image + id: push + uses: docker/build-push-action@v6 + with: + context: glove-build-env + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index f80728d..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose --release - - name: Run tests - run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock index 8846a86..07bf189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3075,6 +3075,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "300.2.3+3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.102" @@ -3083,6 +3092,7 @@ checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/README.md b/README.md index 97c47b5..5807d61 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ If you want to run your own Glove service, you will need to have a compatible AWS EC2 instance with AWS Nitro Enclaves enabled. You can follow the instructions [here](https://docs.aws.amazon.com/enclaves/latest/user/getting-started.html#launch-instance) -to provision the correct EC2 instance. These instructions assume Amazon Linux 2023 on x86_64. Make sure Nitro Enclaves -are enabled. +to provision the correct EC2 instance. These instructions assume Amazon Linux 2023 on x86_64. Make sure the Nitro +Enclaves option is enabled. Then install the [Nitro Enclaves CLI](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave-cli-install.html). -Make sure to allocate at least 1024 MiB for the enclave. +Make sure to allocate at least 512 MiB for the enclave. Install the build tools: @@ -16,12 +16,16 @@ sudo yum groupinstall "Development Tools" -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` +```shell +docker build -t glove-build-env . +``` + Log out and back in again. Build the service binary and enclave image: ```shell -./build-service-enclave.sh +./build.sh ``` ``` @@ -78,6 +82,10 @@ First join Glove with the `join-glove` command and then vote with `vote`. # Development +## MacOS + +If building on MacOS, then use `cargo` directly rather than the build script. Only mock mode will be available. + ## Regenerating the Substrate metadata You first need the `subxt-cli` tool installed: @@ -91,14 +99,3 @@ Then run this in the home directory of this project: ```shell subxt metadata --url="wss://rpc.polkadot.io:443" -f bytes > assets/polkadot-metadata.scale ``` - -## Things to do - -* Sign the enclave image -* Signed Glove proof, again using SCALE encoding -* Persist voting requests -* Restoring state on startup from private store and on-chain -* When does the mixing occur? Is it configurable? -* Remove on-chain votes due to error conditions detected by the proxy -* Split votes -* Abstain votes? diff --git a/build-service-enclave.sh b/build-service-enclave.sh deleted file mode 100755 index cee2c00..0000000 --- a/build-service-enclave.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -Eeuxo pipefail - -cargo build -p service --release -cargo build -p enclave --release -docker build -t glove-enclave -f enclave/Dockerfile . -nitro-cli build-enclave --docker-uri glove-enclave --output-file target/release/glove.eif diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..1ebc43f --- /dev/null +++ b/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -Eeuxo pipefail + +glove_build_env() { + docker exec -t glove-build-env "$@" +} + +rm -rf target +mkdir -p target/release +# TODO use pinned digest of glove-build-env image +docker create --name glove-build-env -v /var/run/docker.sock:/var/run/docker.sock -w /glove glove-build-env +docker cp . glove-build-env:/glove +docker start glove-build-env > /dev/null +glove_build_env git config --global --add safe.directory /glove +glove_build_env cargo test +glove_build_env cargo build --bins -p enclave --target x86_64-unknown-linux-musl -r +glove_build_env cargo build --bins --workspace --exclude enclave -r +glove_build_env touch --date='@0' target/x86_64-unknown-linux-musl/release/enclave +glove_build_env docker build --no-cache -t glove-enclave -f enclave/Dockerfile . +glove_build_env nitro-cli build-enclave --docker-uri glove-enclave --output-file target/release/glove.eif +docker cp glove-build-env:/glove/target . +docker image rm glove-enclave > /dev/null +docker rm -f glove-build-env > /dev/null diff --git a/common/Cargo.toml b/common/Cargo.toml index 5218ea6..e513eea 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -12,7 +12,7 @@ aws-nitro-enclaves-cose.workspace = true aws-nitro-enclaves-nsm-api.workspace = true serde_bytes.workspace = true serde_cbor.workspace = true -openssl.workspace = true +openssl = { workspace = true, features = ["vendored"] } sha2.workspace = true thiserror.workspace = true flate2.workspace = true diff --git a/enclave/Dockerfile b/enclave/Dockerfile index 28351f3..9fcee47 100644 --- a/enclave/Dockerfile +++ b/enclave/Dockerfile @@ -1,7 +1,4 @@ -# TODO Use alpine instead, which will reduce the size of the image and potentially only require 512MB of enclave memory. -# However, this requires building the enclave with musl, which has been a bit tricky to get working on Amazon Linux 2023. -FROM debian:stable-20240612-slim -# TODO If we can't get alphine working then manually install libssl3 from a specific version to keep the build reproducible. -RUN apt-get update && apt-get install libssl3 -y -COPY ../target/release/enclave . +# For reproducible builds, pin the linux distro to an exact digest, here representing v3.20.1 +FROM --platform=linux/amd64 alpine@sha256:dabf91b69c191a1a0a1628fd6bdd029c0c4018041c7f052870bb13c5a222ae76 +COPY ../target/x86_64-unknown-linux-musl/release/enclave . CMD ["./enclave"] diff --git a/enclave/src/main.rs b/enclave/src/main.rs index 25e078a..79ea608 100644 --- a/enclave/src/main.rs +++ b/enclave/src/main.rs @@ -121,7 +121,9 @@ fn process_mix_votes( signing_key: &ed25519::Pair ) -> EnclaveResponse { println!("Received request: {:?}", vote_requests); - let signatures_valid = vote_requests + // TODO This check should be in enclave::mix_votes and tested + // TODO Check no duplicate accounts + let all_valid = vote_requests .iter() .all(|signed_request| signed_request.is_signature_valid()); if signatures_valid { diff --git a/glove-build-env/Dockerfile b/glove-build-env/Dockerfile new file mode 100644 index 0000000..1498fb1 --- /dev/null +++ b/glove-build-env/Dockerfile @@ -0,0 +1,15 @@ +FROM --platform=linux/amd64 amazonlinux:2023 + +ENV PATH="/root/.cargo/bin:$PATH" +ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-amazon-linux-gcc + +RUN yum update -y +RUN dnf install aws-nitro-enclaves-cli -y +RUN dnf install aws-nitro-enclaves-cli-devel -y +RUN yum groupinstall "Development Tools" -y +RUN yum install openssl-devel -y +RUN dnf install perl -y +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +RUN rustup target install x86_64-unknown-linux-musl + +CMD ["sleep", "infinity"] diff --git a/service/src/enclave.rs b/service/src/enclave.rs index 3bf4b5f..980fdf4 100644 --- a/service/src/enclave.rs +++ b/service/src/enclave.rs @@ -41,7 +41,7 @@ pub mod nitro { let mut cmd = Command::new("nitro-cli"); cmd.arg("run-enclave"); cmd.arg("--cpu-count").arg("2"); - cmd.arg("--memory").arg("1024"); + cmd.arg("--memory").arg("512"); cmd.arg("--eif-path").arg(local_file("glove.eif")?); if debug_mode { cmd.arg("--debug-mode"); diff --git a/service/src/main.rs b/service/src/main.rs index 1e66e52..bdb94be 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -85,8 +85,15 @@ enum EnclaveMode { Mock } -// TODO Listen, or poll, for any member who votes directly +// TODO Listen, or poll, for any member who votes directly. +// TODO And only execute on-chain vote batch without them if the service has submitted on-chain already // TODO Test what an actual limit is on batched votes +// TODO Load test with ~100 accounts voting on a single poll +// TODO Sign the enclave image +// TODO Persist voting requests +// TODO Restoring state on startup from private store and on-chain +// TODO When does the mixing occur? Is it configurable? +// TODO Remove on-chain votes due to error conditions detected by the proxy // TODO Deal with RPC disconnect: // 2024-06-19T11:36:12.533696Z DEBUG rustls::common_state: Sending warning alert CloseNotify @@ -203,6 +210,7 @@ async fn info(context: State>) -> Json { // TODO Reject for zero balance // TODO Reject if new vote request reaches max batch size limit for poll +// TODO Reject if voted directly or via another proxy already async fn vote( State(context): State>, Json(payload): Json @@ -302,9 +310,6 @@ fn schedule_vote_mixing(context: Arc, poll: Poll) { }); } -// TODO Add another endpoint which a client can query with their nonce to see the status of -// of their on-chain vote. -// TODO If it's a success then it could include the extrinsic coordinates. async fn mix_votes(context: &GloveContext, poll: &Poll) { loop { match try_mix_votes(context, poll).await {